summaryrefslogtreecommitdiffstats
path: root/graphics
diff options
context:
space:
mode:
authorAlan Viverette <alanv@google.com>2014-11-12 02:04:20 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2014-11-12 02:04:22 +0000
commitb724314516dc15ab7afb62f7a6e63d94f4022011 (patch)
tree31e00e0ef9eaaabb86dd44c54e7e7e0c8780de89 /graphics
parent3f558e7285d8632b81cde4a9f1d0829e02be5fa0 (diff)
parentcc3c573334a9cd2124a8a0ccf2f37884e36f83fa (diff)
downloadframeworks_base-b724314516dc15ab7afb62f7a6e63d94f4022011.zip
frameworks_base-b724314516dc15ab7afb62f7a6e63d94f4022011.tar.gz
frameworks_base-b724314516dc15ab7afb62f7a6e63d94f4022011.tar.bz2
Merge "Reduce number of saveLayer calls in RippleDrawable" into lmp-mr1-dev
Diffstat (limited to 'graphics')
-rw-r--r--graphics/java/android/graphics/drawable/Ripple.java26
-rw-r--r--graphics/java/android/graphics/drawable/RippleBackground.java44
-rw-r--r--graphics/java/android/graphics/drawable/RippleDrawable.java218
3 files changed, 113 insertions, 175 deletions
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 864e119..6731366 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -26,6 +26,7 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
+import android.graphics.Xfermode;
import android.util.MathUtils;
import android.view.HardwareCanvas;
import android.view.RenderNodeAnimator;
@@ -58,8 +59,10 @@ class Ripple {
/** Bounds used for computing max radius. */
private final Rect mBounds;
- /** Full-opacity color for drawing this ripple. */
- private int mColorOpaque;
+ /** ARGB color for drawing this ripple. */
+ private int mColor;
+
+ private Xfermode mXfermode;
/** Maximum ripple radius. */
private float mOuterRadius;
@@ -120,9 +123,7 @@ class Ripple {
mStartingY = startingY;
}
- public void setup(int maxRadius, int color, float density) {
- mColorOpaque = color | 0xFF000000;
-
+ public void setup(int maxRadius, float density) {
if (maxRadius != RippleDrawable.RADIUS_AUTO) {
mHasMaxRadius = true;
mOuterRadius = maxRadius;
@@ -216,6 +217,10 @@ class Ripple {
* Draws the ripple centered at (0,0) using the specified paint.
*/
public boolean draw(Canvas c, Paint p) {
+ // Store the color and xfermode, we might need them later.
+ mColor = p.getColor();
+ mXfermode = p.getXfermode();
+
final boolean canUseHardware = c.isHardwareAccelerated();
if (mCanUseHardware != canUseHardware && mCanUseHardware) {
// We've switched from hardware to non-hardware mode. Panic.
@@ -261,8 +266,8 @@ class Ripple {
private boolean drawSoftware(Canvas c, Paint p) {
boolean hasContent = false;
- p.setColor(mColorOpaque);
- final int alpha = (int) (255 * mOpacity + 0.5f);
+ final int paintAlpha = p.getAlpha();
+ final int alpha = (int) (paintAlpha * mOpacity + 0.5f);
final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
if (alpha > 0 && radius > 0) {
final float x = MathUtils.lerp(
@@ -270,8 +275,8 @@ class Ripple {
final float y = MathUtils.lerp(
mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
p.setAlpha(alpha);
- p.setStyle(Style.FILL);
c.drawCircle(x, y, radius, p);
+ p.setAlpha(paintAlpha);
hasContent = true;
}
@@ -374,8 +379,9 @@ class Ripple {
final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
final Paint paint = getTempPaint();
paint.setAntiAlias(true);
- paint.setColor(mColorOpaque);
- paint.setAlpha((int) (255 * mOpacity + 0.5f));
+ paint.setColor(mColor);
+ paint.setXfermode(mXfermode);
+ paint.setAlpha((int) (Color.alpha(mColor) * mOpacity + 0.5f));
paint.setStyle(Style.FILL);
mPropPaint = CanvasProperty.createPaint(paint);
mPropRadius = CanvasProperty.createFloat(startRadius);
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index 21d865f..69847b5 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -26,6 +26,7 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
+import android.graphics.Xfermode;
import android.util.MathUtils;
import android.view.HardwareCanvas;
import android.view.RenderNodeAnimator;
@@ -60,11 +61,10 @@ class RippleBackground {
/** Bounds used for computing max radius. */
private final Rect mBounds;
- /** Full-opacity color for drawing this ripple. */
- private int mColorOpaque;
+ /** ARGB color for drawing this ripple. */
+ private int mColor;
- /** Maximum alpha value for drawing this ripple. */
- private int mColorAlpha;
+ private Xfermode mXfermode;
/** Maximum ripple radius. */
private float mOuterRadius;
@@ -106,10 +106,7 @@ class RippleBackground {
mBounds = bounds;
}
- public void setup(int maxRadius, int color, float density) {
- mColorOpaque = color | 0xFF000000;
- mColorAlpha = Color.alpha(color) / 2;
-
+ public void setup(int maxRadius, float density) {
if (maxRadius != RippleDrawable.RADIUS_AUTO) {
mHasMaxRadius = true;
mOuterRadius = maxRadius;
@@ -124,10 +121,6 @@ class RippleBackground {
mDensity = density;
}
- public boolean isHardwareAnimating() {
- return mHardwareAnimating;
- }
-
public void onHotspotBoundsChanged() {
if (!mHasMaxRadius) {
final float halfWidth = mBounds.width() / 2.0f;
@@ -151,6 +144,10 @@ class RippleBackground {
* Draws the ripple centered at (0,0) using the specified paint.
*/
public boolean draw(Canvas c, Paint p) {
+ // Store the color and xfermode, we might need them later.
+ mColor = p.getColor();
+ mXfermode = p.getXfermode();
+
final boolean canUseHardware = c.isHardwareAccelerated();
if (mCanUseHardware != canUseHardware && mCanUseHardware) {
// We've switched from hardware to non-hardware mode. Panic.
@@ -169,8 +166,7 @@ class RippleBackground {
}
public boolean shouldDraw() {
- final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
- return mCanUseHardware && mHardwareAnimating || outerAlpha > 0 && mOuterRadius > 0;
+ return (mCanUseHardware && mHardwareAnimating) || (mOuterOpacity > 0 && mOuterRadius > 0);
}
private boolean drawHardware(HardwareCanvas c) {
@@ -201,12 +197,13 @@ class RippleBackground {
private boolean drawSoftware(Canvas c, Paint p) {
boolean hasContent = false;
- p.setColor(mColorOpaque);
- final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
- if (outerAlpha > 0 && mOuterRadius > 0) {
- p.setAlpha(outerAlpha);
- p.setStyle(Style.FILL);
- c.drawCircle(mOuterX, mOuterY, mOuterRadius, p);
+ final int paintAlpha = p.getAlpha();
+ final int alpha = (int) (paintAlpha * mOuterOpacity + 0.5f);
+ final float radius = mOuterRadius;
+ if (alpha > 0 && radius > 0) {
+ p.setAlpha(alpha);
+ c.drawCircle(mOuterX, mOuterY, radius, p);
+ p.setAlpha(paintAlpha);
hasContent = true;
}
@@ -262,7 +259,7 @@ class RippleBackground {
// outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
/ (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
- final int inflectionOpacity = (int) (mColorAlpha * (mOuterOpacity
+ final int inflectionOpacity = (int) (Color.alpha(mColor) * (mOuterOpacity
+ inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
if (mCanUseHardware) {
@@ -277,8 +274,9 @@ class RippleBackground {
final Paint outerPaint = getTempPaint();
outerPaint.setAntiAlias(true);
- outerPaint.setColor(mColorOpaque);
- outerPaint.setAlpha((int) (mColorAlpha * mOuterOpacity + 0.5f));
+ outerPaint.setXfermode(mXfermode);
+ outerPaint.setColor(mColor);
+ outerPaint.setAlpha((int) (Color.alpha(mColor) * mOuterOpacity + 0.5f));
outerPaint.setStyle(Style.FILL);
mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index e658279..8cbc239 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -16,6 +16,11 @@
package android.graphics.drawable;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.ColorStateList;
@@ -34,11 +39,6 @@ import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.IOException;
import java.util.Arrays;
@@ -157,13 +157,6 @@ public class RippleDrawable extends LayerDrawable {
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.
*/
RippleDrawable() {
@@ -203,21 +196,15 @@ public class RippleDrawable extends LayerDrawable {
public void jumpToCurrentState() {
super.jumpToCurrentState();
- boolean needsDraw = false;
-
if (mRipple != null) {
- needsDraw |= mRipple.isHardwareAnimating();
mRipple.jump();
}
if (mBackground != null) {
- needsDraw |= mBackground.isHardwareAnimating();
mBackground.jump();
}
- needsDraw |= cancelExitingRipples();
-
- mNeedsDraw = needsDraw;
+ cancelExitingRipples();
invalidateSelf();
}
@@ -497,8 +484,7 @@ public class RippleDrawable extends LayerDrawable {
mBackground = new RippleBackground(this, mHotspotBounds);
}
- final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
- mBackground.setup(mState.mMaxRadius, color, mDensity);
+ mBackground.setup(mState.mMaxRadius, mDensity);
mBackground.enter(focused);
}
@@ -534,8 +520,7 @@ public class RippleDrawable extends LayerDrawable {
mRipple = new Ripple(this, mHotspotBounds, x, y);
}
- final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
- mRipple.setup(mState.mMaxRadius, color, mDensity);
+ mRipple.setup(mState.mMaxRadius, mDensity);
mRipple.enter();
}
@@ -559,23 +544,17 @@ public class RippleDrawable extends LayerDrawable {
* background. Nothing will be drawn after this method is called.
*/
private void clearHotspots() {
- boolean needsDraw = false;
-
if (mRipple != null) {
- needsDraw |= mRipple.isHardwareAnimating();
mRipple.cancel();
mRipple = null;
}
if (mBackground != null) {
- needsDraw |= mBackground.isHardwareAnimating();
mBackground.cancel();
mBackground = null;
}
- needsDraw |= cancelExitingRipples();
-
- mNeedsDraw = needsDraw;
+ cancelExitingRipples();
invalidateSelf();
}
@@ -631,56 +610,41 @@ public class RippleDrawable extends LayerDrawable {
}
}
+ /**
+ * Optimized for drawing ripples with a mask layer and optional content.
+ */
@Override
public void draw(@NonNull Canvas canvas) {
final boolean hasMask = mMask != null;
- final boolean drawNonMaskContent = mLayerState.mNum > (hasMask ? 1 : 0);
- final boolean drawMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE;
+ final boolean hasRipples = mRipple != null || mExitingRipplesCount > 0
+ || (mBackground != null && mBackground.shouldDraw());
+
+ // Clip to the dirty bounds, which will be the drawable bounds if we
+ // have a mask or content and the ripple bounds if we're projecting.
final Rect bounds = getDirtyBounds();
final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(bounds);
- // If we have content, draw it into a layer first.
- final int contentLayer;
- if (drawNonMaskContent) {
- contentLayer = drawContentLayer(canvas, bounds, SRC_OVER);
- } else {
- contentLayer = -1;
- }
-
- // Next, try to draw the ripples (into a layer if necessary). If we need
- // to mask against the underlying content, set the xfermode to SRC_ATOP.
- final PorterDuffXfermode xfermode = (hasMask || !drawNonMaskContent) ? SRC_OVER : SRC_ATOP;
-
- // If we have a background and a non-opaque mask, draw the masking layer.
- final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode, drawMask);
- if (backgroundLayer >= 0) {
- if (drawMask) {
+ // If we have content, draw it first. If we have ripples and no mask,
+ // we'll draw it into a SRC_OVER layer so that we can mask ripples
+ // against it using SRC_IN.
+ final boolean hasContentLayer = drawContent(canvas, bounds, hasRipples, hasMask);
+
+ // Next, try to draw the ripples. If we have a non-opaque mask, we'll
+ // draw the ripples into a SRC_OVER layer, draw the mask into a DST_IN
+ // layer, and blend.
+ if (hasRipples) {
+ final boolean hasNonOpaqueMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE;
+ final boolean hasRippleLayer = drawBackgroundAndRipples(canvas, bounds,
+ hasNonOpaqueMask, hasContentLayer);
+
+ // If drawing ripples created a layer, we have a non-opaque mask
+ // that needs to be blended on top of the ripples with DST_IN.
+ if (hasRippleLayer) {
drawMaskingLayer(canvas, bounds, DST_IN);
}
- canvas.restoreToCount(backgroundLayer);
}
- // If we have ripples and a non-opaque mask, draw the masking layer.
- final int rippleLayer = drawRippleLayer(canvas, bounds, xfermode);
- if (rippleLayer >= 0) {
- if (drawMask) {
- drawMaskingLayer(canvas, bounds, DST_IN);
- }
- canvas.restoreToCount(rippleLayer);
- }
-
- // If we failed to draw anything and we just canceled animations, at
- // least draw a color so that hardware invalidation works correctly.
- if (contentLayer < 0 && backgroundLayer < 0 && rippleLayer < 0 && mNeedsDraw) {
- canvas.drawColor(Color.TRANSPARENT);
-
- // Request another draw so we can avoid adding a transparent layer
- // during the next display list refresh.
- invalidateSelf();
- }
- mNeedsDraw = false;
-
canvas.restoreToCount(saveCount);
}
@@ -714,28 +678,27 @@ public class RippleDrawable extends LayerDrawable {
return -1;
}
- private int drawContentLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
+ private boolean drawContent(Canvas canvas, Rect bounds, boolean hasRipples, boolean hasMask) {
final ChildDrawable[] array = mLayerState.mChildren;
final int count = mLayerState.mNum;
- // We don't need a layer if we don't expect to draw any ripples or
- // a background, we have an explicit mask, or if the non-mask content
- // is all opaque.
boolean needsLayer = false;
- if ((mExitingRipplesCount > 0 || (mBackground != null && mBackground.shouldDraw()))
- && mMask == null) {
+
+ if (hasRipples && !hasMask) {
+ // If we only have opaque content, we don't really need a layer
+ // because the ripples will be clipped to the drawable bounds.
for (int i = 0; i < count; i++) {
- if (array[i].mId != R.id.mask
- && array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
+ if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
needsLayer = true;
break;
}
}
}
- final Paint maskingPaint = getMaskingPaint(mode);
- final int restoreToCount = needsLayer ? canvas.saveLayer(bounds.left, bounds.top,
- bounds.right, bounds.bottom, maskingPaint) : -1;
+ if (needsLayer) {
+ canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom,
+ getMaskingPaint(SRC_OVER));
+ }
// Draw everything except the mask.
for (int i = 0; i < count; i++) {
@@ -744,82 +707,52 @@ public class RippleDrawable extends LayerDrawable {
}
}
- return restoreToCount;
+ return needsLayer;
}
- private int drawBackgroundLayer(
- Canvas canvas, Rect bounds, PorterDuffXfermode mode, boolean drawMask) {
- int saveCount = -1;
+ 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);
+ }
- if (mBackground != null && mBackground.shouldDraw()) {
- // TODO: We can avoid saveLayer here if we push the xfermode into
- // the background's render thread animator at exit() time.
- if (drawMask || mode != SRC_OVER) {
- saveCount = canvas.saveLayer(bounds.left, bounds.top, bounds.right,
- bounds.bottom, getMaskingPaint(mode));
- }
+ final PorterDuffXfermode mode = hasContentLayer ? SRC_ATOP : SRC_OVER;
+ final float x = mHotspotBounds.exactCenterX();
+ final float y = mHotspotBounds.exactCenterY();
+ canvas.translate(x, y);
- final float x = mHotspotBounds.exactCenterX();
- final float y = mHotspotBounds.exactCenterY();
- canvas.translate(x, y);
- mBackground.draw(canvas, getRipplePaint());
- canvas.translate(-x, -y);
- }
+ final Paint p = getRipplePaint();
+ p.setXfermode(mode);
- return saveCount;
- }
+ // 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);
- private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
- boolean drewRipples = false;
- int restoreToCount = -1;
- int restoreTranslate = -1;
+ final RippleBackground background = mBackground;
+ if (background != null && background.shouldDraw()) {
+ background.draw(canvas, p);
+ }
- // Draw ripples and update the animating ripples array.
final int count = mExitingRipplesCount;
- final Ripple[] ripples = mExitingRipples;
- for (int i = 0; i <= count; i++) {
- final Ripple ripple;
- if (i < count) {
- ripple = ripples[i];
- } else if (mRipple != null) {
- ripple = mRipple;
- } else {
- continue;
- }
-
- // If we're masking the ripple layer, make sure we have a layer
- // first. This will merge SRC_OVER (directly) onto the canvas.
- if (restoreToCount < 0) {
- final Paint maskingPaint = getMaskingPaint(mode);
- final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
- final int alpha = Color.alpha(color);
- maskingPaint.setAlpha(alpha / 2);
-
- // TODO: We can avoid saveLayer here if we're only drawing one
- // ripple and we don't have content or a translucent mask.
- restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
- bounds.right, bounds.bottom, maskingPaint);
-
- // Translate the canvas to the current hotspot bounds.
- restoreTranslate = canvas.save();
- canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());
+ if (count > 0) {
+ final Ripple[] ripples = mExitingRipples;
+ for (int i = 0; i < count; i++) {
+ ripples[i].draw(canvas, p);
}
-
- drewRipples |= ripple.draw(canvas, getRipplePaint());
}
- // Always restore the translation.
- if (restoreTranslate >= 0) {
- canvas.restoreToCount(restoreTranslate);
+ final Ripple active = mRipple;
+ if (active != null) {
+ active.draw(canvas, p);
}
- // If we created a layer with no content, merge it immediately.
- if (restoreToCount >= 0 && !drewRipples) {
- canvas.restoreToCount(restoreToCount);
- restoreToCount = -1;
- }
+ canvas.translate(-x, -y);
- return restoreToCount;
+ // Returns true if a layer was created.
+ return hasNonOpaqueMask;
}
private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
@@ -838,6 +771,7 @@ public class RippleDrawable extends LayerDrawable {
if (mRipplePaint == null) {
mRipplePaint = new Paint();
mRipplePaint.setAntiAlias(true);
+ mRipplePaint.setStyle(Paint.Style.FILL);
}
return mRipplePaint;
}