Merge "Reduce number of saveLayer calls in RippleDrawable" into lmp-mr1-dev automerge: b724314

automerge: 0bc1eee

* commit '0bc1eee096e04d9d0a405961b56569446c0e79ab':
  Reduce number of saveLayer calls in RippleDrawable
This commit is contained in:
Alan Viverette
2014-11-12 03:25:07 +00:00
committed by android-build-merger
3 changed files with 112 additions and 174 deletions

View File

@@ -26,6 +26,7 @@ import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Paint.Style; import android.graphics.Paint.Style;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.Xfermode;
import android.util.MathUtils; import android.util.MathUtils;
import android.view.HardwareCanvas; import android.view.HardwareCanvas;
import android.view.RenderNodeAnimator; import android.view.RenderNodeAnimator;
@@ -58,8 +59,10 @@ class Ripple {
/** Bounds used for computing max radius. */ /** Bounds used for computing max radius. */
private final Rect mBounds; private final Rect mBounds;
/** Full-opacity color for drawing this ripple. */ /** ARGB color for drawing this ripple. */
private int mColorOpaque; private int mColor;
private Xfermode mXfermode;
/** Maximum ripple radius. */ /** Maximum ripple radius. */
private float mOuterRadius; private float mOuterRadius;
@@ -120,9 +123,7 @@ class Ripple {
mStartingY = startingY; mStartingY = startingY;
} }
public void setup(int maxRadius, int color, float density) { public void setup(int maxRadius, float density) {
mColorOpaque = color | 0xFF000000;
if (maxRadius != RippleDrawable.RADIUS_AUTO) { if (maxRadius != RippleDrawable.RADIUS_AUTO) {
mHasMaxRadius = true; mHasMaxRadius = true;
mOuterRadius = maxRadius; mOuterRadius = maxRadius;
@@ -216,6 +217,10 @@ class Ripple {
* Draws the ripple centered at (0,0) using the specified paint. * Draws the ripple centered at (0,0) using the specified paint.
*/ */
public boolean draw(Canvas c, Paint p) { 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(); final boolean canUseHardware = c.isHardwareAccelerated();
if (mCanUseHardware != canUseHardware && mCanUseHardware) { if (mCanUseHardware != canUseHardware && mCanUseHardware) {
// We've switched from hardware to non-hardware mode. Panic. // We've switched from hardware to non-hardware mode. Panic.
@@ -261,8 +266,8 @@ class Ripple {
private boolean drawSoftware(Canvas c, Paint p) { private boolean drawSoftware(Canvas c, Paint p) {
boolean hasContent = false; boolean hasContent = false;
p.setColor(mColorOpaque); final int paintAlpha = p.getAlpha();
final int alpha = (int) (255 * mOpacity + 0.5f); final int alpha = (int) (paintAlpha * mOpacity + 0.5f);
final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
if (alpha > 0 && radius > 0) { if (alpha > 0 && radius > 0) {
final float x = MathUtils.lerp( final float x = MathUtils.lerp(
@@ -270,8 +275,8 @@ class Ripple {
final float y = MathUtils.lerp( final float y = MathUtils.lerp(
mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY); mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
p.setAlpha(alpha); p.setAlpha(alpha);
p.setStyle(Style.FILL);
c.drawCircle(x, y, radius, p); c.drawCircle(x, y, radius, p);
p.setAlpha(paintAlpha);
hasContent = true; hasContent = true;
} }
@@ -374,8 +379,9 @@ class Ripple {
final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
final Paint paint = getTempPaint(); final Paint paint = getTempPaint();
paint.setAntiAlias(true); paint.setAntiAlias(true);
paint.setColor(mColorOpaque); paint.setColor(mColor);
paint.setAlpha((int) (255 * mOpacity + 0.5f)); paint.setXfermode(mXfermode);
paint.setAlpha((int) (Color.alpha(mColor) * mOpacity + 0.5f));
paint.setStyle(Style.FILL); paint.setStyle(Style.FILL);
mPropPaint = CanvasProperty.createPaint(paint); mPropPaint = CanvasProperty.createPaint(paint);
mPropRadius = CanvasProperty.createFloat(startRadius); mPropRadius = CanvasProperty.createFloat(startRadius);

View File

@@ -26,6 +26,7 @@ import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Paint.Style; import android.graphics.Paint.Style;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.Xfermode;
import android.util.MathUtils; import android.util.MathUtils;
import android.view.HardwareCanvas; import android.view.HardwareCanvas;
import android.view.RenderNodeAnimator; import android.view.RenderNodeAnimator;
@@ -60,11 +61,10 @@ class RippleBackground {
/** Bounds used for computing max radius. */ /** Bounds used for computing max radius. */
private final Rect mBounds; private final Rect mBounds;
/** Full-opacity color for drawing this ripple. */ /** ARGB color for drawing this ripple. */
private int mColorOpaque; private int mColor;
/** Maximum alpha value for drawing this ripple. */ private Xfermode mXfermode;
private int mColorAlpha;
/** Maximum ripple radius. */ /** Maximum ripple radius. */
private float mOuterRadius; private float mOuterRadius;
@@ -106,10 +106,7 @@ class RippleBackground {
mBounds = bounds; mBounds = bounds;
} }
public void setup(int maxRadius, int color, float density) { public void setup(int maxRadius, float density) {
mColorOpaque = color | 0xFF000000;
mColorAlpha = Color.alpha(color) / 2;
if (maxRadius != RippleDrawable.RADIUS_AUTO) { if (maxRadius != RippleDrawable.RADIUS_AUTO) {
mHasMaxRadius = true; mHasMaxRadius = true;
mOuterRadius = maxRadius; mOuterRadius = maxRadius;
@@ -124,10 +121,6 @@ class RippleBackground {
mDensity = density; mDensity = density;
} }
public boolean isHardwareAnimating() {
return mHardwareAnimating;
}
public void onHotspotBoundsChanged() { public void onHotspotBoundsChanged() {
if (!mHasMaxRadius) { if (!mHasMaxRadius) {
final float halfWidth = mBounds.width() / 2.0f; final float halfWidth = mBounds.width() / 2.0f;
@@ -151,6 +144,10 @@ class RippleBackground {
* Draws the ripple centered at (0,0) using the specified paint. * Draws the ripple centered at (0,0) using the specified paint.
*/ */
public boolean draw(Canvas c, Paint p) { 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(); final boolean canUseHardware = c.isHardwareAccelerated();
if (mCanUseHardware != canUseHardware && mCanUseHardware) { if (mCanUseHardware != canUseHardware && mCanUseHardware) {
// We've switched from hardware to non-hardware mode. Panic. // We've switched from hardware to non-hardware mode. Panic.
@@ -169,8 +166,7 @@ class RippleBackground {
} }
public boolean shouldDraw() { public boolean shouldDraw() {
final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f); return (mCanUseHardware && mHardwareAnimating) || (mOuterOpacity > 0 && mOuterRadius > 0);
return mCanUseHardware && mHardwareAnimating || outerAlpha > 0 && mOuterRadius > 0;
} }
private boolean drawHardware(HardwareCanvas c) { private boolean drawHardware(HardwareCanvas c) {
@@ -201,12 +197,13 @@ class RippleBackground {
private boolean drawSoftware(Canvas c, Paint p) { private boolean drawSoftware(Canvas c, Paint p) {
boolean hasContent = false; boolean hasContent = false;
p.setColor(mColorOpaque); final int paintAlpha = p.getAlpha();
final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f); final int alpha = (int) (paintAlpha * mOuterOpacity + 0.5f);
if (outerAlpha > 0 && mOuterRadius > 0) { final float radius = mOuterRadius;
p.setAlpha(outerAlpha); if (alpha > 0 && radius > 0) {
p.setStyle(Style.FILL); p.setAlpha(alpha);
c.drawCircle(mOuterX, mOuterY, mOuterRadius, p); c.drawCircle(mOuterX, mOuterY, radius, p);
p.setAlpha(paintAlpha);
hasContent = true; hasContent = true;
} }
@@ -262,7 +259,7 @@ class RippleBackground {
// outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000 // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity) final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
/ (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f)); / (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); + inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
if (mCanUseHardware) { if (mCanUseHardware) {
@@ -277,8 +274,9 @@ class RippleBackground {
final Paint outerPaint = getTempPaint(); final Paint outerPaint = getTempPaint();
outerPaint.setAntiAlias(true); outerPaint.setAntiAlias(true);
outerPaint.setColor(mColorOpaque); outerPaint.setXfermode(mXfermode);
outerPaint.setAlpha((int) (mColorAlpha * mOuterOpacity + 0.5f)); outerPaint.setColor(mColor);
outerPaint.setAlpha((int) (Color.alpha(mColor) * mOuterOpacity + 0.5f));
outerPaint.setStyle(Style.FILL); outerPaint.setStyle(Style.FILL);
mPropOuterPaint = CanvasProperty.createPaint(outerPaint); mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius); mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);

View File

@@ -16,6 +16,11 @@
package android.graphics.drawable; 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.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
@@ -34,11 +39,6 @@ import android.graphics.Rect;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.DisplayMetrics; 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@@ -156,13 +156,6 @@ public class RippleDrawable extends LayerDrawable {
/** Whether bounds are being overridden. */ /** Whether bounds are being overridden. */
private boolean mOverrideBounds; 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. * Constructor used for drawable inflation.
*/ */
@@ -203,21 +196,15 @@ public class RippleDrawable extends LayerDrawable {
public void jumpToCurrentState() { public void jumpToCurrentState() {
super.jumpToCurrentState(); super.jumpToCurrentState();
boolean needsDraw = false;
if (mRipple != null) { if (mRipple != null) {
needsDraw |= mRipple.isHardwareAnimating();
mRipple.jump(); mRipple.jump();
} }
if (mBackground != null) { if (mBackground != null) {
needsDraw |= mBackground.isHardwareAnimating();
mBackground.jump(); mBackground.jump();
} }
needsDraw |= cancelExitingRipples(); cancelExitingRipples();
mNeedsDraw = needsDraw;
invalidateSelf(); invalidateSelf();
} }
@@ -497,8 +484,7 @@ public class RippleDrawable extends LayerDrawable {
mBackground = new RippleBackground(this, mHotspotBounds); mBackground = new RippleBackground(this, mHotspotBounds);
} }
final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT); mBackground.setup(mState.mMaxRadius, mDensity);
mBackground.setup(mState.mMaxRadius, color, mDensity);
mBackground.enter(focused); mBackground.enter(focused);
} }
@@ -534,8 +520,7 @@ public class RippleDrawable extends LayerDrawable {
mRipple = new Ripple(this, mHotspotBounds, x, y); mRipple = new Ripple(this, mHotspotBounds, x, y);
} }
final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT); mRipple.setup(mState.mMaxRadius, mDensity);
mRipple.setup(mState.mMaxRadius, color, mDensity);
mRipple.enter(); mRipple.enter();
} }
@@ -559,23 +544,17 @@ public class RippleDrawable extends LayerDrawable {
* background. Nothing will be drawn after this method is called. * background. Nothing will be drawn after this method is called.
*/ */
private void clearHotspots() { private void clearHotspots() {
boolean needsDraw = false;
if (mRipple != null) { if (mRipple != null) {
needsDraw |= mRipple.isHardwareAnimating();
mRipple.cancel(); mRipple.cancel();
mRipple = null; mRipple = null;
} }
if (mBackground != null) { if (mBackground != null) {
needsDraw |= mBackground.isHardwareAnimating();
mBackground.cancel(); mBackground.cancel();
mBackground = null; mBackground = null;
} }
needsDraw |= cancelExitingRipples(); cancelExitingRipples();
mNeedsDraw = needsDraw;
invalidateSelf(); invalidateSelf();
} }
@@ -631,56 +610,41 @@ public class RippleDrawable extends LayerDrawable {
} }
} }
/**
* Optimized for drawing ripples with a mask layer and optional content.
*/
@Override @Override
public void draw(@NonNull Canvas canvas) { public void draw(@NonNull Canvas canvas) {
final boolean hasMask = mMask != null; final boolean hasMask = mMask != null;
final boolean drawNonMaskContent = mLayerState.mNum > (hasMask ? 1 : 0); final boolean hasRipples = mRipple != null || mExitingRipplesCount > 0
final boolean drawMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE; || (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 Rect bounds = getDirtyBounds();
final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(bounds); canvas.clipRect(bounds);
// If we have content, draw it into a layer first. // If we have content, draw it first. If we have ripples and no mask,
final int contentLayer; // we'll draw it into a SRC_OVER layer so that we can mask ripples
if (drawNonMaskContent) { // against it using SRC_IN.
contentLayer = drawContentLayer(canvas, bounds, SRC_OVER); final boolean hasContentLayer = drawContent(canvas, bounds, hasRipples, hasMask);
} else {
contentLayer = -1;
}
// Next, try to draw the ripples (into a layer if necessary). If we need // Next, try to draw the ripples. If we have a non-opaque mask, we'll
// to mask against the underlying content, set the xfermode to SRC_ATOP. // draw the ripples into a SRC_OVER layer, draw the mask into a DST_IN
final PorterDuffXfermode xfermode = (hasMask || !drawNonMaskContent) ? SRC_OVER : SRC_ATOP; // layer, and blend.
if (hasRipples) {
final boolean hasNonOpaqueMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE;
final boolean hasRippleLayer = drawBackgroundAndRipples(canvas, bounds,
hasNonOpaqueMask, hasContentLayer);
// If we have a background and a non-opaque mask, draw the masking layer. // If drawing ripples created a layer, we have a non-opaque mask
final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode, drawMask); // that needs to be blended on top of the ripples with DST_IN.
if (backgroundLayer >= 0) { if (hasRippleLayer) {
if (drawMask) {
drawMaskingLayer(canvas, bounds, DST_IN); 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); canvas.restoreToCount(saveCount);
} }
@@ -714,28 +678,27 @@ public class RippleDrawable extends LayerDrawable {
return -1; 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 ChildDrawable[] array = mLayerState.mChildren;
final int count = mLayerState.mNum; 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; 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++) { for (int i = 0; i < count; i++) {
if (array[i].mId != R.id.mask if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
&& array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
needsLayer = true; needsLayer = true;
break; break;
} }
} }
} }
final Paint maskingPaint = getMaskingPaint(mode); if (needsLayer) {
final int restoreToCount = needsLayer ? canvas.saveLayer(bounds.left, bounds.top, canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom,
bounds.right, bounds.bottom, maskingPaint) : -1; getMaskingPaint(SRC_OVER));
}
// Draw everything except the mask. // Draw everything except the mask.
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
@@ -744,82 +707,52 @@ public class RippleDrawable extends LayerDrawable {
} }
} }
return restoreToCount; return needsLayer;
} }
private int drawBackgroundLayer( private boolean drawBackgroundAndRipples(
Canvas canvas, Rect bounds, PorterDuffXfermode mode, boolean drawMask) { Canvas canvas, Rect bounds, boolean hasNonOpaqueMask, boolean hasContentLayer) {
int saveCount = -1; if (hasNonOpaqueMask) {
final Paint p = getMaskingPaint(SRC_OVER);
if (mBackground != null && mBackground.shouldDraw()) { canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, p);
// 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 x = mHotspotBounds.exactCenterX();
final float y = mHotspotBounds.exactCenterY(); final float y = mHotspotBounds.exactCenterY();
canvas.translate(x, y); canvas.translate(x, y);
mBackground.draw(canvas, getRipplePaint());
canvas.translate(-x, -y); final Paint p = getRipplePaint();
p.setXfermode(mode);
// 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);
final RippleBackground background = mBackground;
if (background != null && background.shouldDraw()) {
background.draw(canvas, p);
} }
return saveCount;
}
private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
boolean drewRipples = false;
int restoreToCount = -1;
int restoreTranslate = -1;
// Draw ripples and update the animating ripples array.
final int count = mExitingRipplesCount; final int count = mExitingRipplesCount;
if (count > 0) {
final Ripple[] ripples = mExitingRipples; final Ripple[] ripples = mExitingRipples;
for (int i = 0; i <= count; i++) { for (int i = 0; i < count; i++) {
final Ripple ripple; ripples[i].draw(canvas, p);
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 final Ripple active = mRipple;
// first. This will merge SRC_OVER (directly) onto the canvas. if (active != null) {
if (restoreToCount < 0) { active.draw(canvas, p);
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());
} }
drewRipples |= ripple.draw(canvas, getRipplePaint()); canvas.translate(-x, -y);
}
// Always restore the translation. // Returns true if a layer was created.
if (restoreTranslate >= 0) { return hasNonOpaqueMask;
canvas.restoreToCount(restoreTranslate);
}
// If we created a layer with no content, merge it immediately.
if (restoreToCount >= 0 && !drewRipples) {
canvas.restoreToCount(restoreToCount);
restoreToCount = -1;
}
return restoreToCount;
} }
private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
@@ -838,6 +771,7 @@ public class RippleDrawable extends LayerDrawable {
if (mRipplePaint == null) { if (mRipplePaint == null) {
mRipplePaint = new Paint(); mRipplePaint = new Paint();
mRipplePaint.setAntiAlias(true); mRipplePaint.setAntiAlias(true);
mRipplePaint.setStyle(Paint.Style.FILL);
} }
return mRipplePaint; return mRipplePaint;
} }