diff options
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 |
commit | 6dfa60f33ca6018959ebff1efde82db7d2aed1e3 (patch) | |
tree | fb0a111ca70f969f37bc31cc16052dba3a37a86a /graphics | |
parent | 3a0d878ab56475276c61d574af7651820a5cea5a (diff) | |
download | frameworks_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')
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; * <ripple android:color="#ffff0000"> * <item android:id="@android:id/mask" * android:drawable="@android:color/white" /> - * <ripple /></code> + * </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><!-- A blue ripple drawn atop a black rectangle. --/> + * <code><!-- A green ripple drawn atop a black rectangle. --/> * <ripple android:color="#ff00ff00"> * <item android:drawable="@android:color/black" /> - * <ripple /> + * </ripple> * - * <!-- A red ripple drawn atop a drawable resource. --/> - * <ripple android:color="#ff00ff00"> + * <!-- A blue ripple drawn atop a drawable resource. --/> + * <ripple android:color="#ff0000ff"> * <item android:drawable="@drawable/my_drawable" /> - * <ripple /></code> + * </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><!-- An unbounded green ripple. --/> - * <ripple android:color="#ff0000ff" /></code> + * <code><!-- An unbounded red ripple. --/> + * <ripple 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()) { |