Loading core/res/res/values/colors_material.xml +2 −2 Original line number Diff line number Diff line Loading @@ -19,8 +19,8 @@ <color name="background_material_dark">#ff212121</color> <color name="background_material_light">#fffafafa</color> <color name="ripple_material_light">#20444444</color> <color name="ripple_material_dark">#20ffffff</color> <color name="ripple_material_light">#40000000</color> <color name="ripple_material_dark">#40ffffff</color> <color name="button_material_dark">#ff5a595b</color> <color name="button_material_light">#ffd6d7d7</color> Loading graphics/java/android/graphics/drawable/Ripple.java +16 −27 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ 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; Loading Loading @@ -58,7 +59,7 @@ class Ripple { private final Rect mBounds; /** Full-opacity color for drawing this ripple. */ private int mColor; private int mColorOpaque; /** Maximum ripple radius. */ private float mOuterRadius; Loading Loading @@ -120,7 +121,7 @@ class Ripple { } public void setup(int maxRadius, int color, float density) { mColor = color | 0xFF000000; mColorOpaque = color | 0xFF000000; if (maxRadius != RippleDrawable.RADIUS_AUTO) { mHasMaxRadius = true; Loading Loading @@ -236,6 +237,9 @@ class Ripple { if (N > 0) { 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(); Loading @@ -253,9 +257,8 @@ class Ripple { private boolean drawSoftware(Canvas c, Paint p) { boolean hasContent = false; // Cache the paint alpha so we can restore it later. final int paintAlpha = p.getAlpha(); final int alpha = (int) (paintAlpha * mOpacity + 0.5f); p.setColor(mColorOpaque); final int alpha = (int) (255 * mOpacity + 0.5f); final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); if (alpha > 0 && radius > 0) { final float x = MathUtils.lerp( Loading @@ -268,8 +271,6 @@ class Ripple { hasContent = true; } p.setAlpha(paintAlpha); return hasContent; } Loading Loading @@ -369,7 +370,7 @@ class Ripple { final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); final Paint paint = getTempPaint(); paint.setAntiAlias(true); paint.setColor(mColor); paint.setColor(mColorOpaque); paint.setAlpha((int) (255 * mOpacity + 0.5f)); paint.setStyle(Style.FILL); mPropPaint = CanvasProperty.createPaint(paint); Loading Loading @@ -402,6 +403,12 @@ class Ripple { mHardwareAnimating = true; // Set up the software values to match the hardware end values. mOpacity = 0; mTweenX = 1; mTweenY = 1; mTweenRadius = 1; invalidateSelf(); } Loading @@ -412,7 +419,7 @@ class Ripple { public void jump() { mCanceled = true; endSoftwareAnimations(); endHardwareAnimations(); cancelHardwareAnimations(true); mCanceled = false; } Loading @@ -438,24 +445,6 @@ class Ripple { } } private void endHardwareAnimations() { final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations; final int N = runningAnimations.size(); for (int i = 0; i < N; i++) { runningAnimations.get(i).end(); } runningAnimations.clear(); // Abort any pending animations. Since we always have a completion // listener on a pending animation, we also need to remove ourselves. if (!mPendingAnimations.isEmpty()) { mPendingAnimations.clear(); removeSelf(); } mHardwareAnimating = false; } private Paint getTempPaint() { if (mTempPaint == null) { mTempPaint = new Paint(); Loading graphics/java/android/graphics/drawable/RippleBackground.java +36 −56 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ 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; Loading @@ -46,8 +47,6 @@ class RippleBackground { private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f; private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f; private static final long RIPPLE_ENTER_DELAY = 80; // Hardware animators. private final ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<RenderNodeAnimator>(); Loading @@ -60,7 +59,10 @@ class RippleBackground { private final Rect mBounds; /** Full-opacity color for drawing this ripple. */ private int mColor; private int mColorOpaque; /** Maximum alpha value for drawing this ripple. */ private int mColorAlpha; /** Maximum ripple radius. */ private float mOuterRadius; Loading Loading @@ -103,7 +105,8 @@ class RippleBackground { } public void setup(int maxRadius, int color, float density) { mColor = color | 0xFF000000; mColorOpaque = color | 0xFF000000; mColorAlpha = Color.alpha(color); if (maxRadius != RippleDrawable.RADIUS_AUTO) { mHasMaxRadius = true; Loading Loading @@ -159,6 +162,11 @@ class RippleBackground { return hasContent; } public boolean shouldDraw() { final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f); return mCanUseHardware && mHardwareAnimating || outerAlpha > 0 && mOuterRadius > 0; } private boolean drawHardware(HardwareCanvas c) { // If we have any pending hardware animations, cancel any running // animations and start those now. Loading @@ -167,6 +175,9 @@ class RippleBackground { if (N > 0) { 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(); Loading @@ -184,10 +195,8 @@ class RippleBackground { private boolean drawSoftware(Canvas c, Paint p) { boolean hasContent = false; // Cache the paint alpha so we can restore it later. final int paintAlpha = p.getAlpha(); final int outerAlpha = (int) (paintAlpha * mOuterOpacity + 0.5f); p.setColor(mColorOpaque); final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f); if (outerAlpha > 0 && mOuterRadius > 0) { p.setAlpha(outerAlpha); p.setStyle(Style.FILL); Loading @@ -195,8 +204,6 @@ class RippleBackground { hasContent = true; } p.setAlpha(paintAlpha); return hasContent; } Loading Loading @@ -248,25 +255,25 @@ class RippleBackground { // Determine at what time the inner and outer opacity intersect. // inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000 // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000 final int outerInflection = Math.max(0, (int) (1000 * (1 - mOuterOpacity) final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity) / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f)); final int inflectionOpacity = (int) (255 * (mOuterOpacity + outerInflection * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f); final int inflectionOpacity = (int) (mColorAlpha * (mOuterOpacity + inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f); if (mCanUseHardware) { exitHardware(opacityDuration, outerInflection, inflectionOpacity); exitHardware(opacityDuration, inflectionDuration, inflectionOpacity); } else { exitSoftware(opacityDuration, outerInflection, inflectionOpacity); exitSoftware(opacityDuration, inflectionDuration, inflectionOpacity); } } private void exitHardware(int opacityDuration, int outerInflection, int inflectionOpacity) { private void exitHardware(int opacityDuration, int inflectionDuration, int inflectionOpacity) { mPendingAnimations.clear(); final Paint outerPaint = getTempPaint(); outerPaint.setAntiAlias(true); outerPaint.setColor(mColor); outerPaint.setAlpha((int) (255 * mOuterOpacity + 0.5f)); outerPaint.setColor(mColorOpaque); outerPaint.setAlpha((int) (mColorAlpha * mOuterOpacity + 0.5f)); outerPaint.setStyle(Style.FILL); mPropOuterPaint = CanvasProperty.createPaint(outerPaint); mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius); Loading @@ -274,21 +281,21 @@ class RippleBackground { mPropOuterY = CanvasProperty.createFloat(mOuterY); final RenderNodeAnimator outerOpacityAnim; if (outerInflection > 0) { if (inflectionDuration > 0) { // Outer opacity continues to increase for a bit. outerOpacityAnim = new RenderNodeAnimator( mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity); outerOpacityAnim.setDuration(outerInflection); outerOpacityAnim = new RenderNodeAnimator(mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity); outerOpacityAnim.setDuration(inflectionDuration); outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR); // Chain the outer opacity exit animation. final int outerDuration = opacityDuration - outerInflection; final int outerDuration = opacityDuration - inflectionDuration; if (outerDuration > 0) { final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator( mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0); outerFadeOutAnim.setDuration(outerDuration); outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR); outerFadeOutAnim.setStartDelay(outerInflection); outerFadeOutAnim.setStartDelay(inflectionDuration); outerFadeOutAnim.setStartValue(inflectionOpacity); outerFadeOutAnim.addListener(mAnimationListener); Loading Loading @@ -320,7 +327,7 @@ class RippleBackground { */ public void jump() { endSoftwareAnimations(); endHardwareAnimations(); cancelHardwareAnimations(true); } private void endSoftwareAnimations() { Loading @@ -330,23 +337,6 @@ class RippleBackground { } } private void endHardwareAnimations() { final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations; final int N = runningAnimations.size(); for (int i = 0; i < N; i++) { runningAnimations.get(i).end(); } runningAnimations.clear(); // Abort any pending animations. Since we always have a completion // listener on a pending animation, we also need to remove ourselves. if (!mPendingAnimations.isEmpty()) { mPendingAnimations.clear(); } mHardwareAnimating = false; } private Paint getTempPaint() { if (mTempPaint == null) { mTempPaint = new Paint(); Loading @@ -354,18 +344,18 @@ class RippleBackground { return mTempPaint; } private void exitSoftware(int opacityDuration, int outerInflection, int inflectionOpacity) { private void exitSoftware(int opacityDuration, int inflectionDuration, int inflectionOpacity) { final ObjectAnimator outerOpacityAnim; if (outerInflection > 0) { if (inflectionDuration > 0) { // Outer opacity continues to increase for a bit. outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", inflectionOpacity / 255.0f); outerOpacityAnim.setAutoCancel(true); outerOpacityAnim.setDuration(outerInflection); outerOpacityAnim.setDuration(inflectionDuration); outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR); // Chain the outer opacity exit animation. final int outerDuration = opacityDuration - outerInflection; final int outerDuration = opacityDuration - inflectionDuration; if (outerDuration > 0) { outerOpacityAnim.addListener(new AnimatorListenerAdapter() { @Override Loading Loading @@ -446,14 +436,4 @@ class RippleBackground { mHardwareAnimating = false; } }; /** * Interpolator with a smooth log deceleration */ private static final class LogInterpolator implements TimeInterpolator { @Override public float getInterpolation(float input) { return 1 - (float) Math.pow(400, -input * 1.4); } } } graphics/java/android/graphics/drawable/RippleDrawable.java +53 −91 Original line number Diff line number Diff line Loading @@ -242,7 +242,7 @@ public class RippleDrawable extends LayerDrawable { @Override protected boolean onStateChange(int[] stateSet) { super.onStateChange(stateSet); final boolean changed = super.onStateChange(stateSet); boolean enabled = false; boolean pressed = false; Loading @@ -263,19 +263,7 @@ public class RippleDrawable extends LayerDrawable { setRippleActive(enabled && pressed); setBackgroundActive(focused || (enabled && pressed)); // Update the paint color. Only applicable when animated in software. if (mRipplePaint != null && mState.mColor != null) { final ColorStateList stateList = mState.mColor; final int newColor = stateList.getColorForState(stateSet, 0); final int oldColor = mRipplePaint.getColor(); if (oldColor != newColor) { mRipplePaint.setColor(newColor); invalidateSelf(); return true; } } return false; return changed; } private void setRippleActive(boolean active) { Loading Loading @@ -587,6 +575,10 @@ public class RippleDrawable extends LayerDrawable { ripples[i].onHotspotBoundsChanged(); } if (mRipple != null) { mRipple.onHotspotBoundsChanged(); } if (mBackground != null) { mBackground.onHotspotBoundsChanged(); } Loading Loading @@ -617,17 +609,23 @@ public class RippleDrawable extends LayerDrawable { final boolean drawNonMaskContent = mLayerState.mNum > (hasMask ? 1 : 0); final boolean drawMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE; 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 = drawNonMaskContent ? drawContentLayer(canvas, bounds, SRC_OVER) : -1; 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); final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode, drawMask); if (backgroundLayer >= 0) { if (drawMask) { drawMaskingLayer(canvas, bounds, DST_IN); Loading @@ -644,10 +642,13 @@ public class RippleDrawable extends LayerDrawable { canvas.restoreToCount(rippleLayer); } // Composite the layers if needed. if (contentLayer >= 0) { canvas.restoreToCount(contentLayer); // If we failed to draw anything, at least draw a color so that // invalidation works correctly. if (contentLayer < 0 && backgroundLayer < 0 && rippleLayer < 0) { canvas.drawColor(Color.TRANSPARENT); } canvas.restoreToCount(saveCount); } /** Loading Loading @@ -711,81 +712,29 @@ public class RippleDrawable extends LayerDrawable { return restoreToCount; } private int drawBackgroundLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { // Separate the ripple color and alpha channel. The alpha will be // applied when we merge the ripples down to the canvas. final int rippleARGB; if (mState.mColor != null) { rippleARGB = mState.mColor.getColorForState(getState(), Color.TRANSPARENT); } else { rippleARGB = Color.TRANSPARENT; } private int drawBackgroundLayer( Canvas canvas, Rect bounds, PorterDuffXfermode mode, boolean drawMask) { int saveCount = -1; if (mRipplePaint == null) { mRipplePaint = new Paint(); mRipplePaint.setAntiAlias(true); 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 int rippleAlpha = Color.alpha(rippleARGB); final Paint ripplePaint = mRipplePaint; ripplePaint.setColor(rippleARGB); ripplePaint.setAlpha(0xFF); boolean drewRipples = false; int restoreToCount = -1; int restoreTranslate = -1; // Draw background. final RippleBackground background = mBackground; if (background != null) { // If we're masking the ripple layer, make sure we have a layer // first. This will merge SRC_OVER (directly) onto the canvas. final Paint maskingPaint = getMaskingPaint(mode); maskingPaint.setAlpha(rippleAlpha); restoreToCount = canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, maskingPaint); restoreTranslate = canvas.save(); // Translate the canvas to the current hotspot bounds. canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY()); drewRipples = background.draw(canvas, ripplePaint); final float x = mHotspotBounds.exactCenterX(); final float y = mHotspotBounds.exactCenterY(); canvas.translate(x, y); mBackground.draw(canvas, getRipplePaint()); canvas.translate(-x, -y); } // Always restore the translation. if (restoreTranslate >= 0) { canvas.restoreToCount(restoreTranslate); } // If we created a layer with no content, merge it immediately. if (restoreToCount >= 0 && !drewRipples) { canvas.restoreToCount(restoreToCount); restoreToCount = -1; } return restoreToCount; return saveCount; } private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { // Separate the ripple color and alpha channel. The alpha will be // applied when we merge the ripples down to the canvas. final int rippleARGB; if (mState.mColor != null) { rippleARGB = mState.mColor.getColorForState(getState(), Color.TRANSPARENT); } else { rippleARGB = Color.TRANSPARENT; } if (mRipplePaint == null) { mRipplePaint = new Paint(); mRipplePaint.setAntiAlias(true); } final int rippleAlpha = Color.alpha(rippleARGB); final Paint ripplePaint = mRipplePaint; ripplePaint.setColor(rippleARGB); ripplePaint.setAlpha(0xFF); boolean drewRipples = false; int restoreToCount = -1; int restoreTranslate = -1; Loading @@ -807,16 +756,21 @@ public class RippleDrawable extends LayerDrawable { // first. This will merge SRC_OVER (directly) onto the canvas. if (restoreToCount < 0) { final Paint maskingPaint = getMaskingPaint(mode); maskingPaint.setAlpha(rippleAlpha); 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); restoreTranslate = canvas.save(); // Translate the canvas to the current hotspot bounds. restoreTranslate = canvas.save(); canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY()); } drewRipples |= ripple.draw(canvas, ripplePaint); drewRipples |= ripple.draw(canvas, getRipplePaint()); } // Always restore the translation. Loading Loading @@ -845,6 +799,14 @@ public class RippleDrawable extends LayerDrawable { return restoreToCount; } private Paint getRipplePaint() { if (mRipplePaint == null) { mRipplePaint = new Paint(); mRipplePaint.setAntiAlias(true); } return mRipplePaint; } private Paint getMaskingPaint(PorterDuffXfermode xfermode) { if (mMaskingPaint == null) { mMaskingPaint = new Paint(); Loading Loading
core/res/res/values/colors_material.xml +2 −2 Original line number Diff line number Diff line Loading @@ -19,8 +19,8 @@ <color name="background_material_dark">#ff212121</color> <color name="background_material_light">#fffafafa</color> <color name="ripple_material_light">#20444444</color> <color name="ripple_material_dark">#20ffffff</color> <color name="ripple_material_light">#40000000</color> <color name="ripple_material_dark">#40ffffff</color> <color name="button_material_dark">#ff5a595b</color> <color name="button_material_light">#ffd6d7d7</color> Loading
graphics/java/android/graphics/drawable/Ripple.java +16 −27 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ 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; Loading Loading @@ -58,7 +59,7 @@ class Ripple { private final Rect mBounds; /** Full-opacity color for drawing this ripple. */ private int mColor; private int mColorOpaque; /** Maximum ripple radius. */ private float mOuterRadius; Loading Loading @@ -120,7 +121,7 @@ class Ripple { } public void setup(int maxRadius, int color, float density) { mColor = color | 0xFF000000; mColorOpaque = color | 0xFF000000; if (maxRadius != RippleDrawable.RADIUS_AUTO) { mHasMaxRadius = true; Loading Loading @@ -236,6 +237,9 @@ class Ripple { if (N > 0) { 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(); Loading @@ -253,9 +257,8 @@ class Ripple { private boolean drawSoftware(Canvas c, Paint p) { boolean hasContent = false; // Cache the paint alpha so we can restore it later. final int paintAlpha = p.getAlpha(); final int alpha = (int) (paintAlpha * mOpacity + 0.5f); p.setColor(mColorOpaque); final int alpha = (int) (255 * mOpacity + 0.5f); final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); if (alpha > 0 && radius > 0) { final float x = MathUtils.lerp( Loading @@ -268,8 +271,6 @@ class Ripple { hasContent = true; } p.setAlpha(paintAlpha); return hasContent; } Loading Loading @@ -369,7 +370,7 @@ class Ripple { final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); final Paint paint = getTempPaint(); paint.setAntiAlias(true); paint.setColor(mColor); paint.setColor(mColorOpaque); paint.setAlpha((int) (255 * mOpacity + 0.5f)); paint.setStyle(Style.FILL); mPropPaint = CanvasProperty.createPaint(paint); Loading Loading @@ -402,6 +403,12 @@ class Ripple { mHardwareAnimating = true; // Set up the software values to match the hardware end values. mOpacity = 0; mTweenX = 1; mTweenY = 1; mTweenRadius = 1; invalidateSelf(); } Loading @@ -412,7 +419,7 @@ class Ripple { public void jump() { mCanceled = true; endSoftwareAnimations(); endHardwareAnimations(); cancelHardwareAnimations(true); mCanceled = false; } Loading @@ -438,24 +445,6 @@ class Ripple { } } private void endHardwareAnimations() { final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations; final int N = runningAnimations.size(); for (int i = 0; i < N; i++) { runningAnimations.get(i).end(); } runningAnimations.clear(); // Abort any pending animations. Since we always have a completion // listener on a pending animation, we also need to remove ourselves. if (!mPendingAnimations.isEmpty()) { mPendingAnimations.clear(); removeSelf(); } mHardwareAnimating = false; } private Paint getTempPaint() { if (mTempPaint == null) { mTempPaint = new Paint(); Loading
graphics/java/android/graphics/drawable/RippleBackground.java +36 −56 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ 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; Loading @@ -46,8 +47,6 @@ class RippleBackground { private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f; private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f; private static final long RIPPLE_ENTER_DELAY = 80; // Hardware animators. private final ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<RenderNodeAnimator>(); Loading @@ -60,7 +59,10 @@ class RippleBackground { private final Rect mBounds; /** Full-opacity color for drawing this ripple. */ private int mColor; private int mColorOpaque; /** Maximum alpha value for drawing this ripple. */ private int mColorAlpha; /** Maximum ripple radius. */ private float mOuterRadius; Loading Loading @@ -103,7 +105,8 @@ class RippleBackground { } public void setup(int maxRadius, int color, float density) { mColor = color | 0xFF000000; mColorOpaque = color | 0xFF000000; mColorAlpha = Color.alpha(color); if (maxRadius != RippleDrawable.RADIUS_AUTO) { mHasMaxRadius = true; Loading Loading @@ -159,6 +162,11 @@ class RippleBackground { return hasContent; } public boolean shouldDraw() { final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f); return mCanUseHardware && mHardwareAnimating || outerAlpha > 0 && mOuterRadius > 0; } private boolean drawHardware(HardwareCanvas c) { // If we have any pending hardware animations, cancel any running // animations and start those now. Loading @@ -167,6 +175,9 @@ class RippleBackground { if (N > 0) { 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(); Loading @@ -184,10 +195,8 @@ class RippleBackground { private boolean drawSoftware(Canvas c, Paint p) { boolean hasContent = false; // Cache the paint alpha so we can restore it later. final int paintAlpha = p.getAlpha(); final int outerAlpha = (int) (paintAlpha * mOuterOpacity + 0.5f); p.setColor(mColorOpaque); final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f); if (outerAlpha > 0 && mOuterRadius > 0) { p.setAlpha(outerAlpha); p.setStyle(Style.FILL); Loading @@ -195,8 +204,6 @@ class RippleBackground { hasContent = true; } p.setAlpha(paintAlpha); return hasContent; } Loading Loading @@ -248,25 +255,25 @@ class RippleBackground { // Determine at what time the inner and outer opacity intersect. // inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000 // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000 final int outerInflection = Math.max(0, (int) (1000 * (1 - mOuterOpacity) final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity) / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f)); final int inflectionOpacity = (int) (255 * (mOuterOpacity + outerInflection * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f); final int inflectionOpacity = (int) (mColorAlpha * (mOuterOpacity + inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f); if (mCanUseHardware) { exitHardware(opacityDuration, outerInflection, inflectionOpacity); exitHardware(opacityDuration, inflectionDuration, inflectionOpacity); } else { exitSoftware(opacityDuration, outerInflection, inflectionOpacity); exitSoftware(opacityDuration, inflectionDuration, inflectionOpacity); } } private void exitHardware(int opacityDuration, int outerInflection, int inflectionOpacity) { private void exitHardware(int opacityDuration, int inflectionDuration, int inflectionOpacity) { mPendingAnimations.clear(); final Paint outerPaint = getTempPaint(); outerPaint.setAntiAlias(true); outerPaint.setColor(mColor); outerPaint.setAlpha((int) (255 * mOuterOpacity + 0.5f)); outerPaint.setColor(mColorOpaque); outerPaint.setAlpha((int) (mColorAlpha * mOuterOpacity + 0.5f)); outerPaint.setStyle(Style.FILL); mPropOuterPaint = CanvasProperty.createPaint(outerPaint); mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius); Loading @@ -274,21 +281,21 @@ class RippleBackground { mPropOuterY = CanvasProperty.createFloat(mOuterY); final RenderNodeAnimator outerOpacityAnim; if (outerInflection > 0) { if (inflectionDuration > 0) { // Outer opacity continues to increase for a bit. outerOpacityAnim = new RenderNodeAnimator( mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity); outerOpacityAnim.setDuration(outerInflection); outerOpacityAnim = new RenderNodeAnimator(mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity); outerOpacityAnim.setDuration(inflectionDuration); outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR); // Chain the outer opacity exit animation. final int outerDuration = opacityDuration - outerInflection; final int outerDuration = opacityDuration - inflectionDuration; if (outerDuration > 0) { final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator( mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0); outerFadeOutAnim.setDuration(outerDuration); outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR); outerFadeOutAnim.setStartDelay(outerInflection); outerFadeOutAnim.setStartDelay(inflectionDuration); outerFadeOutAnim.setStartValue(inflectionOpacity); outerFadeOutAnim.addListener(mAnimationListener); Loading Loading @@ -320,7 +327,7 @@ class RippleBackground { */ public void jump() { endSoftwareAnimations(); endHardwareAnimations(); cancelHardwareAnimations(true); } private void endSoftwareAnimations() { Loading @@ -330,23 +337,6 @@ class RippleBackground { } } private void endHardwareAnimations() { final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations; final int N = runningAnimations.size(); for (int i = 0; i < N; i++) { runningAnimations.get(i).end(); } runningAnimations.clear(); // Abort any pending animations. Since we always have a completion // listener on a pending animation, we also need to remove ourselves. if (!mPendingAnimations.isEmpty()) { mPendingAnimations.clear(); } mHardwareAnimating = false; } private Paint getTempPaint() { if (mTempPaint == null) { mTempPaint = new Paint(); Loading @@ -354,18 +344,18 @@ class RippleBackground { return mTempPaint; } private void exitSoftware(int opacityDuration, int outerInflection, int inflectionOpacity) { private void exitSoftware(int opacityDuration, int inflectionDuration, int inflectionOpacity) { final ObjectAnimator outerOpacityAnim; if (outerInflection > 0) { if (inflectionDuration > 0) { // Outer opacity continues to increase for a bit. outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", inflectionOpacity / 255.0f); outerOpacityAnim.setAutoCancel(true); outerOpacityAnim.setDuration(outerInflection); outerOpacityAnim.setDuration(inflectionDuration); outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR); // Chain the outer opacity exit animation. final int outerDuration = opacityDuration - outerInflection; final int outerDuration = opacityDuration - inflectionDuration; if (outerDuration > 0) { outerOpacityAnim.addListener(new AnimatorListenerAdapter() { @Override Loading Loading @@ -446,14 +436,4 @@ class RippleBackground { mHardwareAnimating = false; } }; /** * Interpolator with a smooth log deceleration */ private static final class LogInterpolator implements TimeInterpolator { @Override public float getInterpolation(float input) { return 1 - (float) Math.pow(400, -input * 1.4); } } }
graphics/java/android/graphics/drawable/RippleDrawable.java +53 −91 Original line number Diff line number Diff line Loading @@ -242,7 +242,7 @@ public class RippleDrawable extends LayerDrawable { @Override protected boolean onStateChange(int[] stateSet) { super.onStateChange(stateSet); final boolean changed = super.onStateChange(stateSet); boolean enabled = false; boolean pressed = false; Loading @@ -263,19 +263,7 @@ public class RippleDrawable extends LayerDrawable { setRippleActive(enabled && pressed); setBackgroundActive(focused || (enabled && pressed)); // Update the paint color. Only applicable when animated in software. if (mRipplePaint != null && mState.mColor != null) { final ColorStateList stateList = mState.mColor; final int newColor = stateList.getColorForState(stateSet, 0); final int oldColor = mRipplePaint.getColor(); if (oldColor != newColor) { mRipplePaint.setColor(newColor); invalidateSelf(); return true; } } return false; return changed; } private void setRippleActive(boolean active) { Loading Loading @@ -587,6 +575,10 @@ public class RippleDrawable extends LayerDrawable { ripples[i].onHotspotBoundsChanged(); } if (mRipple != null) { mRipple.onHotspotBoundsChanged(); } if (mBackground != null) { mBackground.onHotspotBoundsChanged(); } Loading Loading @@ -617,17 +609,23 @@ public class RippleDrawable extends LayerDrawable { final boolean drawNonMaskContent = mLayerState.mNum > (hasMask ? 1 : 0); final boolean drawMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE; 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 = drawNonMaskContent ? drawContentLayer(canvas, bounds, SRC_OVER) : -1; 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); final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode, drawMask); if (backgroundLayer >= 0) { if (drawMask) { drawMaskingLayer(canvas, bounds, DST_IN); Loading @@ -644,10 +642,13 @@ public class RippleDrawable extends LayerDrawable { canvas.restoreToCount(rippleLayer); } // Composite the layers if needed. if (contentLayer >= 0) { canvas.restoreToCount(contentLayer); // If we failed to draw anything, at least draw a color so that // invalidation works correctly. if (contentLayer < 0 && backgroundLayer < 0 && rippleLayer < 0) { canvas.drawColor(Color.TRANSPARENT); } canvas.restoreToCount(saveCount); } /** Loading Loading @@ -711,81 +712,29 @@ public class RippleDrawable extends LayerDrawable { return restoreToCount; } private int drawBackgroundLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { // Separate the ripple color and alpha channel. The alpha will be // applied when we merge the ripples down to the canvas. final int rippleARGB; if (mState.mColor != null) { rippleARGB = mState.mColor.getColorForState(getState(), Color.TRANSPARENT); } else { rippleARGB = Color.TRANSPARENT; } private int drawBackgroundLayer( Canvas canvas, Rect bounds, PorterDuffXfermode mode, boolean drawMask) { int saveCount = -1; if (mRipplePaint == null) { mRipplePaint = new Paint(); mRipplePaint.setAntiAlias(true); 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 int rippleAlpha = Color.alpha(rippleARGB); final Paint ripplePaint = mRipplePaint; ripplePaint.setColor(rippleARGB); ripplePaint.setAlpha(0xFF); boolean drewRipples = false; int restoreToCount = -1; int restoreTranslate = -1; // Draw background. final RippleBackground background = mBackground; if (background != null) { // If we're masking the ripple layer, make sure we have a layer // first. This will merge SRC_OVER (directly) onto the canvas. final Paint maskingPaint = getMaskingPaint(mode); maskingPaint.setAlpha(rippleAlpha); restoreToCount = canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, maskingPaint); restoreTranslate = canvas.save(); // Translate the canvas to the current hotspot bounds. canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY()); drewRipples = background.draw(canvas, ripplePaint); final float x = mHotspotBounds.exactCenterX(); final float y = mHotspotBounds.exactCenterY(); canvas.translate(x, y); mBackground.draw(canvas, getRipplePaint()); canvas.translate(-x, -y); } // Always restore the translation. if (restoreTranslate >= 0) { canvas.restoreToCount(restoreTranslate); } // If we created a layer with no content, merge it immediately. if (restoreToCount >= 0 && !drewRipples) { canvas.restoreToCount(restoreToCount); restoreToCount = -1; } return restoreToCount; return saveCount; } private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { // Separate the ripple color and alpha channel. The alpha will be // applied when we merge the ripples down to the canvas. final int rippleARGB; if (mState.mColor != null) { rippleARGB = mState.mColor.getColorForState(getState(), Color.TRANSPARENT); } else { rippleARGB = Color.TRANSPARENT; } if (mRipplePaint == null) { mRipplePaint = new Paint(); mRipplePaint.setAntiAlias(true); } final int rippleAlpha = Color.alpha(rippleARGB); final Paint ripplePaint = mRipplePaint; ripplePaint.setColor(rippleARGB); ripplePaint.setAlpha(0xFF); boolean drewRipples = false; int restoreToCount = -1; int restoreTranslate = -1; Loading @@ -807,16 +756,21 @@ public class RippleDrawable extends LayerDrawable { // first. This will merge SRC_OVER (directly) onto the canvas. if (restoreToCount < 0) { final Paint maskingPaint = getMaskingPaint(mode); maskingPaint.setAlpha(rippleAlpha); 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); restoreTranslate = canvas.save(); // Translate the canvas to the current hotspot bounds. restoreTranslate = canvas.save(); canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY()); } drewRipples |= ripple.draw(canvas, ripplePaint); drewRipples |= ripple.draw(canvas, getRipplePaint()); } // Always restore the translation. Loading Loading @@ -845,6 +799,14 @@ public class RippleDrawable extends LayerDrawable { return restoreToCount; } private Paint getRipplePaint() { if (mRipplePaint == null) { mRipplePaint = new Paint(); mRipplePaint.setAntiAlias(true); } return mRipplePaint; } private Paint getMaskingPaint(PorterDuffXfermode xfermode) { if (mMaskingPaint == null) { mMaskingPaint = new Paint(); Loading