summaryrefslogtreecommitdiffstats
path: root/graphics
diff options
context:
space:
mode:
authorAlan Viverette <alanv@google.com>2014-12-04 14:10:16 -0800
committerAlan Viverette <alanv@google.com>2014-12-04 16:52:16 -0800
commit6dfa60f33ca6018959ebff1efde82db7d2aed1e3 (patch)
treefb0a111ca70f969f37bc31cc16052dba3a37a86a /graphics
parent3a0d878ab56475276c61d574af7651820a5cea5a (diff)
downloadframeworks_base-6dfa60f33ca6018959ebff1efde82db7d2aed1e3.zip
frameworks_base-6dfa60f33ca6018959ebff1efde82db7d2aed1e3.tar.gz
frameworks_base-6dfa60f33ca6018959ebff1efde82db7d2aed1e3.tar.bz2
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
Diffstat (limited to 'graphics')
-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
4 files changed, 253 insertions, 196 deletions
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index b88d9e6..acfd427 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 6731366..a3a220c 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 69847b5..665d736 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 d5d5d51..13e3d54 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()) {