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
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
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);
|
||||
// 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(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
drawContent(canvas);
|
||||
drawBackgroundAndRipples(canvas);
|
||||
|
||||
// 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);
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
|
||||
// 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);
|
||||
@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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
canvas.restoreToCount(saveCount);
|
||||
// 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()) {
|
||||
|
||||
Reference in New Issue
Block a user