summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Alan Viverette <alanv@google.com> 2014-12-04 14:10:16 -0800
committer Alan Viverette <alanv@google.com> 2014-12-04 16:52:16 -0800
commit6dfa60f33ca6018959ebff1efde82db7d2aed1e3 (patch)
treefb0a111ca70f969f37bc31cc16052dba3a37a86a
parent3a0d878ab56475276c61d574af7651820a5cea5a (diff)
Avoid extra saveLayer calls in RippleDrawable, fix docs
Also fixes opacity returned from InsetDrawable to accurately reflect the transparent inset area and updates button to correctly use tint. BUG: 18226391 Change-Id: Ia9a88d9d663990a6829d2f251c7f59ea2a79d816
-rw-r--r--core/res/res/drawable/btn_default_mtrl_shape.xml5
-rw-r--r--graphics/java/android/graphics/drawable/InsetDrawable.java9
-rw-r--r--graphics/java/android/graphics/drawable/Ripple.java98
-rw-r--r--graphics/java/android/graphics/drawable/RippleBackground.java85
-rw-r--r--graphics/java/android/graphics/drawable/RippleDrawable.java257
5 files changed, 256 insertions, 198 deletions
diff --git a/core/res/res/drawable/btn_default_mtrl_shape.xml b/core/res/res/drawable/btn_default_mtrl_shape.xml
index 6d0f7f8c63c7..8a31d5ece132 100644
--- a/core/res/res/drawable/btn_default_mtrl_shape.xml
+++ b/core/res/res/drawable/btn_default_mtrl_shape.xml
@@ -21,9 +21,10 @@
android:insetTop="@dimen/button_inset_vertical_material"
android:insetRight="@dimen/button_inset_horizontal_material"
android:insetBottom="@dimen/button_inset_vertical_material">
- <shape android:shape="rectangle">
+ <shape android:shape="rectangle"
+ android:tint="?attr/colorButtonNormal">
<corners android:radius="@dimen/control_corner_material" />
- <solid android:color="?attr/colorButtonNormal" />
+ <solid android:color="@color/white" />
<padding android:left="@dimen/button_padding_horizontal_material"
android:top="@dimen/button_padding_vertical_material"
android:right="@dimen/button_padding_horizontal_material"
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index b88d9e67b069..acfd427ebc93 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -30,6 +30,7 @@ import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Insets;
import android.graphics.Outline;
+import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -317,7 +318,13 @@ public class InsetDrawable extends Drawable implements Drawable.Callback {
@Override
public int getOpacity() {
- return mState.mDrawable.getOpacity();
+ final InsetState state = mState;
+ final int opacity = state.mDrawable.getOpacity();
+ if (opacity == PixelFormat.OPAQUE && (state.mInsetLeft > 0 || state.mInsetTop > 0
+ || state.mInsetRight > 0 || state.mInsetBottom > 0)) {
+ return PixelFormat.TRANSLUCENT;
+ }
+ return opacity;
}
@Override
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 6731366f8a9a..a3a220c4b3c4 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -22,11 +22,8 @@ import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
-import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.Paint.Style;
import android.graphics.Rect;
-import android.graphics.Xfermode;
import android.util.MathUtils;
import android.view.HardwareCanvas;
import android.view.RenderNodeAnimator;
@@ -51,19 +48,12 @@ class Ripple {
// Hardware animators.
private final ArrayList<RenderNodeAnimator> mRunningAnimations =
new ArrayList<RenderNodeAnimator>();
- private final ArrayList<RenderNodeAnimator> mPendingAnimations =
- new ArrayList<RenderNodeAnimator>();
private final RippleDrawable mOwner;
/** Bounds used for computing max radius. */
private final Rect mBounds;
- /** ARGB color for drawing this ripple. */
- private int mColor;
-
- private Xfermode mXfermode;
-
/** Maximum ripple radius. */
private float mOuterRadius;
@@ -112,6 +102,10 @@ class Ripple {
/** Whether we were canceled externally and should avoid self-removal. */
private boolean mCanceled;
+ private boolean mHasPendingHardwareExit;
+ private int mPendingRadiusDuration;
+ private int mPendingOpacityDuration;
+
/**
* Creates a new ripple.
*/
@@ -217,10 +211,6 @@ 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.
@@ -229,8 +219,8 @@ class Ripple {
mCanUseHardware = canUseHardware;
final boolean hasContent;
- if (canUseHardware && mHardwareAnimating) {
- hasContent = drawHardware((HardwareCanvas) c);
+ if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) {
+ hasContent = drawHardware((HardwareCanvas) c, p);
} else {
hasContent = drawSoftware(c, p);
}
@@ -238,24 +228,10 @@ class Ripple {
return hasContent;
}
- private boolean drawHardware(HardwareCanvas c) {
- // If we have any pending hardware animations, cancel any running
- // animations and start those now.
- final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
- final int N = pendingAnimations.size();
- if (N > 0) {
+ private boolean drawHardware(HardwareCanvas c, Paint p) {
+ if (mHasPendingHardwareExit) {
cancelHardwareAnimations(false);
-
- // We canceled old animations, but we're about to run new ones.
- mHardwareAnimating = true;
-
- for (int i = 0; i < N; i++) {
- pendingAnimations.get(i).setTarget(c);
- pendingAnimations.get(i).start();
- }
-
- mRunningAnimations.addAll(pendingAnimations);
- pendingAnimations.clear();
+ startPendingHardwareExit(c, p);
}
c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
@@ -347,8 +323,6 @@ class Ripple {
* Starts the exit animation.
*/
public void exit() {
- cancel();
-
final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
final float remaining;
if (mAnimRadius != null && mAnimRadius.isRunning()) {
@@ -357,19 +331,33 @@ class Ripple {
remaining = mOuterRadius;
}
+ cancel();
+
final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
+ WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
if (mCanUseHardware) {
- exitHardware(radiusDuration, opacityDuration);
+ createPendingHardwareExit(radiusDuration, opacityDuration);
} else {
exitSoftware(radiusDuration, opacityDuration);
}
}
- private void exitHardware(int radiusDuration, int opacityDuration) {
- mPendingAnimations.clear();
+ private void createPendingHardwareExit(int radiusDuration, int opacityDuration) {
+ mHasPendingHardwareExit = true;
+ mPendingRadiusDuration = radiusDuration;
+ mPendingOpacityDuration = opacityDuration;
+
+ // The animation will start on the next draw().
+ invalidateSelf();
+ }
+
+ private void startPendingHardwareExit(HardwareCanvas c, Paint p) {
+ mHasPendingHardwareExit = false;
+
+ final int radiusDuration = mPendingRadiusDuration;
+ final int opacityDuration = mPendingOpacityDuration;
final float startX = MathUtils.lerp(
mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
@@ -377,12 +365,8 @@ class Ripple {
mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
- final Paint paint = getTempPaint();
- paint.setAntiAlias(true);
- paint.setColor(mColor);
- paint.setXfermode(mXfermode);
- paint.setAlpha((int) (Color.alpha(mColor) * mOpacity + 0.5f));
- paint.setStyle(Style.FILL);
+ final Paint paint = getTempPaint(p);
+ paint.setAlpha((int) (paint.getAlpha() * mOpacity + 0.5f));
mPropPaint = CanvasProperty.createPaint(paint);
mPropRadius = CanvasProperty.createFloat(startRadius);
mPropX = CanvasProperty.createFloat(startX);
@@ -391,25 +375,33 @@ class Ripple {
final RenderNodeAnimator radiusAnim = new RenderNodeAnimator(mPropRadius, mOuterRadius);
radiusAnim.setDuration(radiusDuration);
radiusAnim.setInterpolator(DECEL_INTERPOLATOR);
+ radiusAnim.setTarget(c);
+ radiusAnim.start();
final RenderNodeAnimator xAnim = new RenderNodeAnimator(mPropX, mOuterX);
xAnim.setDuration(radiusDuration);
xAnim.setInterpolator(DECEL_INTERPOLATOR);
+ xAnim.setTarget(c);
+ xAnim.start();
final RenderNodeAnimator yAnim = new RenderNodeAnimator(mPropY, mOuterY);
yAnim.setDuration(radiusDuration);
yAnim.setInterpolator(DECEL_INTERPOLATOR);
+ yAnim.setTarget(c);
+ yAnim.start();
final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPropPaint,
RenderNodeAnimator.PAINT_ALPHA, 0);
opacityAnim.setDuration(opacityDuration);
opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
opacityAnim.addListener(mAnimationListener);
+ opacityAnim.setTarget(c);
+ opacityAnim.start();
- mPendingAnimations.add(radiusAnim);
- mPendingAnimations.add(opacityAnim);
- mPendingAnimations.add(xAnim);
- mPendingAnimations.add(yAnim);
+ mRunningAnimations.add(radiusAnim);
+ mRunningAnimations.add(opacityAnim);
+ mRunningAnimations.add(xAnim);
+ mRunningAnimations.add(yAnim);
mHardwareAnimating = true;
@@ -418,8 +410,6 @@ class Ripple {
mTweenX = 1;
mTweenY = 1;
mTweenRadius = 1;
-
- invalidateSelf();
}
/**
@@ -455,10 +445,11 @@ class Ripple {
}
}
- private Paint getTempPaint() {
+ private Paint getTempPaint(Paint original) {
if (mTempPaint == null) {
mTempPaint = new Paint();
}
+ mTempPaint.set(original);
return mTempPaint;
}
@@ -539,10 +530,7 @@ class Ripple {
}
runningAnimations.clear();
- if (cancelPending && !mPendingAnimations.isEmpty()) {
- mPendingAnimations.clear();
- }
-
+ mHasPendingHardwareExit = false;
mHardwareAnimating = false;
}
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index 69847b54cd44..665d736d985d 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -24,9 +24,7 @@ import android.graphics.Canvas;
import android.graphics.CanvasProperty;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.Paint.Style;
import android.graphics.Rect;
-import android.graphics.Xfermode;
import android.util.MathUtils;
import android.view.HardwareCanvas;
import android.view.RenderNodeAnimator;
@@ -53,8 +51,6 @@ class RippleBackground {
// Hardware animators.
private final ArrayList<RenderNodeAnimator> mRunningAnimations =
new ArrayList<RenderNodeAnimator>();
- private final ArrayList<RenderNodeAnimator> mPendingAnimations =
- new ArrayList<RenderNodeAnimator>();
private final RippleDrawable mOwner;
@@ -64,8 +60,6 @@ class RippleBackground {
/** ARGB color for drawing this ripple. */
private int mColor;
- private Xfermode mXfermode;
-
/** Maximum ripple radius. */
private float mOuterRadius;
@@ -98,6 +92,11 @@ class RippleBackground {
/** Whether we have an explicit maximum radius. */
private boolean mHasMaxRadius;
+ private boolean mHasPendingHardwareExit;
+ private int mPendingOpacityDuration;
+ private int mPendingInflectionDuration;
+ private int mPendingInflectionOpacity;
+
/**
* Creates a new ripple.
*/
@@ -144,9 +143,7 @@ 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) {
@@ -156,8 +153,8 @@ class RippleBackground {
mCanUseHardware = canUseHardware;
final boolean hasContent;
- if (canUseHardware && mHardwareAnimating) {
- hasContent = drawHardware((HardwareCanvas) c);
+ if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) {
+ hasContent = drawHardware((HardwareCanvas) c, p);
} else {
hasContent = drawSoftware(c, p);
}
@@ -169,24 +166,10 @@ class RippleBackground {
return (mCanUseHardware && mHardwareAnimating) || (mOuterOpacity > 0 && mOuterRadius > 0);
}
- private boolean drawHardware(HardwareCanvas c) {
- // If we have any pending hardware animations, cancel any running
- // animations and start those now.
- final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
- final int N = pendingAnimations.size();
- if (N > 0) {
+ private boolean drawHardware(HardwareCanvas c, Paint p) {
+ if (mHasPendingHardwareExit) {
cancelHardwareAnimations(false);
-
- // We canceled old animations, but we're about to run new ones.
- mHardwareAnimating = true;
-
- for (int i = 0; i < N; i++) {
- pendingAnimations.get(i).setTarget(c);
- pendingAnimations.get(i).start();
- }
-
- mRunningAnimations.addAll(pendingAnimations);
- pendingAnimations.clear();
+ startPendingHardwareExit(c, p);
}
c.drawCircle(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint);
@@ -263,21 +246,32 @@ class RippleBackground {
+ inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
if (mCanUseHardware) {
- exitHardware(opacityDuration, inflectionDuration, inflectionOpacity);
+ createPendingHardwareExit(opacityDuration, inflectionDuration, inflectionOpacity);
} else {
exitSoftware(opacityDuration, inflectionDuration, inflectionOpacity);
}
}
- private void exitHardware(int opacityDuration, int inflectionDuration, int inflectionOpacity) {
- mPendingAnimations.clear();
+ private void createPendingHardwareExit(
+ int opacityDuration, int inflectionDuration, int inflectionOpacity) {
+ mHasPendingHardwareExit = true;
+ mPendingOpacityDuration = opacityDuration;
+ mPendingInflectionDuration = inflectionDuration;
+ mPendingInflectionOpacity = inflectionOpacity;
+
+ // The animation will start on the next draw().
+ invalidateSelf();
+ }
+
+ private void startPendingHardwareExit(HardwareCanvas c, Paint p) {
+ mHasPendingHardwareExit = false;
+
+ final int opacityDuration = mPendingOpacityDuration;
+ final int inflectionDuration = mPendingInflectionDuration;
+ final int inflectionOpacity = mPendingInflectionOpacity;
- final Paint outerPaint = getTempPaint();
- outerPaint.setAntiAlias(true);
- outerPaint.setXfermode(mXfermode);
- outerPaint.setColor(mColor);
- outerPaint.setAlpha((int) (Color.alpha(mColor) * mOuterOpacity + 0.5f));
- outerPaint.setStyle(Style.FILL);
+ final Paint outerPaint = getTempPaint(p);
+ outerPaint.setAlpha((int) (outerPaint.getAlpha() * mOuterOpacity + 0.5f));
mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
mPropOuterX = CanvasProperty.createFloat(mOuterX);
@@ -301,8 +295,10 @@ class RippleBackground {
outerFadeOutAnim.setStartDelay(inflectionDuration);
outerFadeOutAnim.setStartValue(inflectionOpacity);
outerFadeOutAnim.addListener(mAnimationListener);
+ outerFadeOutAnim.setTarget(c);
+ outerFadeOutAnim.start();
- mPendingAnimations.add(outerFadeOutAnim);
+ mRunningAnimations.add(outerFadeOutAnim);
} else {
outerOpacityAnim.addListener(mAnimationListener);
}
@@ -314,14 +310,15 @@ class RippleBackground {
outerOpacityAnim.addListener(mAnimationListener);
}
- mPendingAnimations.add(outerOpacityAnim);
+ outerOpacityAnim.setTarget(c);
+ outerOpacityAnim.start();
+
+ mRunningAnimations.add(outerOpacityAnim);
mHardwareAnimating = true;
// Set up the software values to match the hardware end values.
mOuterOpacity = 0;
-
- invalidateSelf();
}
/**
@@ -340,10 +337,11 @@ class RippleBackground {
}
}
- private Paint getTempPaint() {
+ private Paint getTempPaint(Paint original) {
if (mTempPaint == null) {
mTempPaint = new Paint();
}
+ mTempPaint.set(original);
return mTempPaint;
}
@@ -422,10 +420,7 @@ class RippleBackground {
}
runningAnimations.clear();
- if (cancelPending && !mPendingAnimations.isEmpty()) {
- mPendingAnimations.clear();
- }
-
+ mHasPendingHardwareExit = false;
mHardwareAnimating = false;
}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index d5d5d5139b74..13e3d54c3476 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -27,15 +27,19 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
+import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
+import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -56,7 +60,7 @@ import java.util.Arrays;
* &ltripple android:color="#ffff0000">
* &ltitem android:id="@android:id/mask"
* android:drawable="@android:color/white" />
- * &ltripple /></code>
+ * &lt/ripple></code>
* </pre>
* <p>
* If a mask layer is set, the ripple effect will be masked against that layer
@@ -65,15 +69,15 @@ import java.util.Arrays;
* If no mask layer is set, the ripple effect is masked against the composite
* of the child layers.
* <pre>
- * <code>&lt!-- A blue ripple drawn atop a black rectangle. --/>
+ * <code>&lt!-- A green ripple drawn atop a black rectangle. --/>
* &ltripple android:color="#ff00ff00">
* &ltitem android:drawable="@android:color/black" />
- * &ltripple />
+ * &lt/ripple>
*
- * &lt!-- A red ripple drawn atop a drawable resource. --/>
- * &ltripple android:color="#ff00ff00">
+ * &lt!-- A blue ripple drawn atop a drawable resource. --/>
+ * &ltripple android:color="#ff0000ff">
* &ltitem android:drawable="@drawable/my_drawable" />
- * &ltripple /></code>
+ * &lt/ripple></code>
* </pre>
* <p>
* If no child layers or mask is specified and the ripple is set as a View
@@ -81,16 +85,17 @@ import java.util.Arrays;
* background within the View's hierarchy. In this case, the drawing region
* may extend outside of the Drawable bounds.
* <pre>
- * <code>&lt!-- An unbounded green ripple. --/>
- * &ltripple android:color="#ff0000ff" /></code>
+ * <code>&lt!-- An unbounded red ripple. --/>
+ * &ltripple android:color="#ffff0000" /></code>
* </pre>
*
* @attr ref android.R.styleable#RippleDrawable_color
*/
public class RippleDrawable extends LayerDrawable {
- private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
- private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP);
- private static final PorterDuffXfermode SRC_OVER = new PorterDuffXfermode(Mode.SRC_OVER);
+ private static final int MASK_UNKNOWN = -1;
+ private static final int MASK_NONE = 0;
+ private static final int MASK_CONTENT = 1;
+ private static final int MASK_EXPLICIT = 2;
/**
* Constant for automatically determining the maximum ripple radius.
@@ -123,6 +128,13 @@ public class RippleDrawable extends LayerDrawable {
/** The current background. May be actively animating or pending entry. */
private RippleBackground mBackground;
+ private Bitmap mMaskBuffer;
+ private BitmapShader mMaskShader;
+ private Canvas mMaskCanvas;
+ private Matrix mMaskMatrix;
+ private PorterDuffColorFilter mMaskColorFilter;
+ private boolean mHasValidMask;
+
/** Whether we expect to draw a background when visible. */
private boolean mBackgroundActive;
@@ -147,9 +159,6 @@ public class RippleDrawable extends LayerDrawable {
/** Paint used to control appearance of ripples. */
private Paint mRipplePaint;
- /** Paint used to control reveal layer masking. */
- private Paint mMaskingPaint;
-
/** Target density of the display into which ripples are drawn. */
private float mDensity = 1.0f;
@@ -615,37 +624,116 @@ public class RippleDrawable extends LayerDrawable {
*/
@Override
public void draw(@NonNull Canvas canvas) {
- final boolean hasMask = mMask != null;
- 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 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);
+ drawContent(canvas);
+ drawBackgroundAndRipples(canvas);
+
+ canvas.restoreToCount(saveCount);
+ }
+
+ @Override
+ public void invalidateSelf() {
+ super.invalidateSelf();
+
+ // Force the mask to update on the next draw().
+ mHasValidMask = false;
+ }
+
+ /**
+ * @return whether we need to use a mask
+ */
+ private void updateMaskShaderIfNeeded() {
+ if (mHasValidMask) {
+ return;
+ }
+
+ final int maskType = getMaskType();
+ if (maskType == MASK_UNKNOWN) {
+ return;
+ }
+
+ mHasValidMask = true;
+
+ if (maskType == MASK_NONE) {
+ if (mMaskBuffer != null) {
+ mMaskBuffer.recycle();
+ mMaskBuffer = null;
+ mMaskShader = null;
+ mMaskCanvas = null;
}
+ mMaskMatrix = null;
+ mMaskColorFilter = null;
+ return;
}
- canvas.restoreToCount(saveCount);
+ // Ensure we have a correctly-sized buffer.
+ final Rect bounds = getBounds();
+ if (mMaskBuffer == null
+ || mMaskBuffer.getWidth() != bounds.width()
+ || mMaskBuffer.getHeight() != bounds.height()) {
+ if (mMaskBuffer != null) {
+ mMaskBuffer.recycle();
+ }
+
+ mMaskBuffer = Bitmap.createBitmap(
+ bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8);
+ mMaskShader = new BitmapShader(mMaskBuffer,
+ Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+ mMaskCanvas = new Canvas(mMaskBuffer);
+ } else {
+ mMaskBuffer.eraseColor(Color.TRANSPARENT);
+ }
+
+ if (mMaskMatrix == null) {
+ mMaskMatrix = new Matrix();
+ } else {
+ mMaskMatrix.reset();
+ }
+
+ if (mMaskColorFilter == null) {
+ mMaskColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_IN);
+ }
+
+ // Draw the appropriate mask.
+ if (maskType == MASK_EXPLICIT) {
+ drawMask(mMaskCanvas);
+ } else if (maskType == MASK_CONTENT) {
+ drawContent(mMaskCanvas);
+ }
+ }
+
+ private int getMaskType() {
+ if (mRipple == null && mExitingRipplesCount <= 0
+ && (mBackground == null || !mBackground.shouldDraw())) {
+ // We might need a mask later.
+ return MASK_UNKNOWN;
+ }
+
+ if (mMask != null) {
+ if (mMask.getOpacity() == PixelFormat.OPAQUE) {
+ // Clipping handles opaque explicit masks.
+ return MASK_NONE;
+ } else {
+ return MASK_EXPLICIT;
+ }
+ }
+
+ // Check for non-opaque, non-mask content.
+ final ChildDrawable[] array = mLayerState.mChildren;
+ final int count = mLayerState.mNum;
+ for (int i = 0; i < count; i++) {
+ if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
+ return MASK_CONTENT;
+ }
+ }
+
+ // Clipping handles opaque content.
+ return MASK_NONE;
}
/**
@@ -678,65 +766,65 @@ public class RippleDrawable extends LayerDrawable {
return -1;
}
- private boolean drawContent(Canvas canvas, Rect bounds, boolean hasRipples, boolean hasMask) {
+ private void drawContent(Canvas canvas) {
+ // Draw everything except the mask.
final ChildDrawable[] array = mLayerState.mChildren;
final int count = mLayerState.mNum;
-
- boolean needsLayer = false;
-
- 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].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
- needsLayer = true;
- break;
- }
- }
- }
-
- 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++) {
if (array[i].mId != R.id.mask) {
array[i].mDrawable.draw(canvas);
}
}
-
- return needsLayer;
}
- 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);
+ private void drawBackgroundAndRipples(Canvas canvas) {
+ final Ripple active = mRipple;
+ final RippleBackground background = mBackground;
+ final int count = mExitingRipplesCount;
+ if (active == null && count <= 0 && (background == null || !background.shouldDraw())) {
+ // Move along, nothing to draw here.
+ return;
}
- final PorterDuffXfermode mode = hasContentLayer ? SRC_ATOP : SRC_OVER;
final float x = mHotspotBounds.exactCenterX();
final float y = mHotspotBounds.exactCenterY();
canvas.translate(x, y);
- final Paint p = getRipplePaint();
- p.setXfermode(mode);
+ updateMaskShaderIfNeeded();
+
+ // Position the shader to account for canvas translation.
+ if (mMaskShader != null) {
+ mMaskMatrix.setTranslate(-x, -y);
+ mMaskShader.setLocalMatrix(mMaskMatrix);
+ }
// 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 int halfAlpha = (Color.alpha(color) / 2) << 24;
+ final Paint p = getRipplePaint();
+
+ if (mMaskColorFilter != null) {
+ // The ripple timing depends on the paint's alpha value, so we need
+ // to push just the alpha channel into the paint and let the filter
+ // handle the full-alpha color.
+ final int fullAlphaColor = color | (0xFF << 24);
+ mMaskColorFilter.setColor(fullAlphaColor);
+
+ p.setColor(halfAlpha);
+ p.setColorFilter(mMaskColorFilter);
+ p.setShader(mMaskShader);
+ } else {
+ final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
+ p.setColor(halfAlphaColor);
+ p.setColorFilter(null);
+ p.setShader(null);
+ }
- final RippleBackground background = mBackground;
if (background != null && background.shouldDraw()) {
background.draw(canvas, p);
}
- final int count = mExitingRipplesCount;
if (count > 0) {
final Ripple[] ripples = mExitingRipples;
for (int i = 0; i < count; i++) {
@@ -744,27 +832,15 @@ public class RippleDrawable extends LayerDrawable {
}
}
- final Ripple active = mRipple;
if (active != null) {
active.draw(canvas, p);
}
canvas.translate(-x, -y);
-
- // Returns true if a layer was created.
- return hasNonOpaqueMask;
}
- private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
- final int restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
- bounds.right, bounds.bottom, getMaskingPaint(mode));
-
- // Ensure that DST_IN blends using the entire layer.
- canvas.drawColor(Color.TRANSPARENT);
-
+ private void drawMask(Canvas canvas) {
mMask.draw(canvas);
-
- return restoreToCount;
}
private Paint getRipplePaint() {
@@ -776,15 +852,6 @@ public class RippleDrawable extends LayerDrawable {
return mRipplePaint;
}
- private Paint getMaskingPaint(PorterDuffXfermode xfermode) {
- if (mMaskingPaint == null) {
- mMaskingPaint = new Paint();
- }
- mMaskingPaint.setXfermode(xfermode);
- mMaskingPaint.setAlpha(0xFF);
- return mMaskingPaint;
- }
-
@Override
public Rect getDirtyBounds() {
if (isProjected()) {