Merge "Separate background from ripple for better focus/press UX" into lmp-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
f43192c845
@@ -28,7 +28,6 @@ import android.graphics.Rect;
|
||||
import android.util.MathUtils;
|
||||
import android.view.HardwareCanvas;
|
||||
import android.view.RenderNodeAnimator;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -44,16 +43,14 @@ class Ripple {
|
||||
private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024.0f * GLOBAL_SPEED;
|
||||
private static final float WAVE_TOUCH_UP_ACCELERATION = 3400.0f * GLOBAL_SPEED;
|
||||
private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED;
|
||||
private static final float WAVE_OUTER_OPACITY_VELOCITY_MAX = 4.5f * GLOBAL_SPEED;
|
||||
private static final float WAVE_OUTER_OPACITY_VELOCITY_MIN = 1.5f * GLOBAL_SPEED;
|
||||
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<>();
|
||||
private final ArrayList<RenderNodeAnimator> mPendingAnimations = new ArrayList<>();
|
||||
private final ArrayList<RenderNodeAnimator> mRunningAnimations =
|
||||
new ArrayList<RenderNodeAnimator>();
|
||||
private final ArrayList<RenderNodeAnimator> mPendingAnimations =
|
||||
new ArrayList<RenderNodeAnimator>();
|
||||
|
||||
private final RippleDrawable mOwner;
|
||||
|
||||
@@ -79,20 +76,17 @@ class Ripple {
|
||||
private CanvasProperty<Float> mPropRadius;
|
||||
private CanvasProperty<Float> mPropX;
|
||||
private CanvasProperty<Float> mPropY;
|
||||
private CanvasProperty<Paint> mPropOuterPaint;
|
||||
private CanvasProperty<Float> mPropOuterRadius;
|
||||
private CanvasProperty<Float> mPropOuterX;
|
||||
private CanvasProperty<Float> mPropOuterY;
|
||||
|
||||
// Software animators.
|
||||
private ObjectAnimator mAnimRadius;
|
||||
private ObjectAnimator mAnimOpacity;
|
||||
private ObjectAnimator mAnimOuterOpacity;
|
||||
private ObjectAnimator mAnimX;
|
||||
private ObjectAnimator mAnimY;
|
||||
|
||||
// Temporary paint used for creating canvas properties.
|
||||
private Paint mTempPaint;
|
||||
|
||||
// Software rendering properties.
|
||||
private float mOuterOpacity = 0;
|
||||
private float mOpacity = 1;
|
||||
private float mOuterX;
|
||||
private float mOuterY;
|
||||
@@ -177,38 +171,35 @@ class Ripple {
|
||||
return mOpacity;
|
||||
}
|
||||
|
||||
public void setOuterOpacity(float a) {
|
||||
mOuterOpacity = a;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getOuterOpacity() {
|
||||
return mOuterOpacity;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setRadiusGravity(float r) {
|
||||
mTweenRadius = r;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public float getRadiusGravity() {
|
||||
return mTweenRadius;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setXGravity(float x) {
|
||||
mTweenX = x;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public float getXGravity() {
|
||||
return mTweenX;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setYGravity(float y) {
|
||||
mTweenY = y;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public float getYGravity() {
|
||||
return mTweenY;
|
||||
}
|
||||
@@ -238,7 +229,7 @@ class Ripple {
|
||||
// If we have any pending hardware animations, cancel any running
|
||||
// animations and start those now.
|
||||
final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
|
||||
final int N = pendingAnimations == null ? 0 : pendingAnimations.size();
|
||||
final int N = pendingAnimations.size();
|
||||
if (N > 0) {
|
||||
cancelHardwareAnimations();
|
||||
|
||||
@@ -251,7 +242,6 @@ class Ripple {
|
||||
pendingAnimations.clear();
|
||||
}
|
||||
|
||||
c.drawCircle(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint);
|
||||
c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
|
||||
|
||||
return true;
|
||||
@@ -262,15 +252,6 @@ class Ripple {
|
||||
|
||||
// Cache the paint alpha so we can restore it later.
|
||||
final int paintAlpha = p.getAlpha();
|
||||
|
||||
final int outerAlpha = (int) (paintAlpha * mOuterOpacity + 0.5f);
|
||||
if (outerAlpha > 0 && mOuterRadius > 0) {
|
||||
p.setAlpha(outerAlpha);
|
||||
p.setStyle(Style.FILL);
|
||||
c.drawCircle(mOuterX, mOuterY, mOuterRadius, p);
|
||||
hasContent = true;
|
||||
}
|
||||
|
||||
final int alpha = (int) (paintAlpha * mOpacity + 0.5f);
|
||||
final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
|
||||
if (alpha > 0 && radius > 0) {
|
||||
@@ -316,7 +297,6 @@ class Ripple {
|
||||
public void enter() {
|
||||
final int radiusDuration = (int)
|
||||
(1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
|
||||
final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_VELOCITY_MIN);
|
||||
|
||||
final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
|
||||
radius.setAutoCancel(true);
|
||||
@@ -336,13 +316,7 @@ class Ripple {
|
||||
cY.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
cY.setStartDelay(RIPPLE_ENTER_DELAY);
|
||||
|
||||
final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
|
||||
outer.setAutoCancel(true);
|
||||
outer.setDuration(outerDuration);
|
||||
outer.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
|
||||
mAnimRadius = radius;
|
||||
mAnimOuterOpacity = outer;
|
||||
mAnimX = cX;
|
||||
mAnimY = cY;
|
||||
|
||||
@@ -350,7 +324,6 @@ class Ripple {
|
||||
// that anything interesting is happening until the user lifts their
|
||||
// finger.
|
||||
radius.start();
|
||||
outer.start();
|
||||
cX.start();
|
||||
cY.start();
|
||||
}
|
||||
@@ -372,51 +345,23 @@ class Ripple {
|
||||
+ WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
|
||||
final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
|
||||
|
||||
// Scale the outer max opacity and opacity velocity based
|
||||
// on the size of the outer radius
|
||||
|
||||
float outerSizeInfluence = MathUtils.constrain(
|
||||
(mOuterRadius - WAVE_OUTER_SIZE_INFLUENCE_MIN * mDensity)
|
||||
/ (WAVE_OUTER_SIZE_INFLUENCE_MAX * mDensity), 0, 1);
|
||||
float outerOpacityVelocity = MathUtils.lerp(WAVE_OUTER_OPACITY_VELOCITY_MIN,
|
||||
WAVE_OUTER_OPACITY_VELOCITY_MAX, outerSizeInfluence);
|
||||
|
||||
// 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 * (mOpacity - mOuterOpacity)
|
||||
/ (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
|
||||
final int inflectionOpacity = (int) (255 * (mOuterOpacity + outerInflection
|
||||
* outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
|
||||
|
||||
if (mCanUseHardware) {
|
||||
exitHardware(radiusDuration, opacityDuration, outerInflection, inflectionOpacity);
|
||||
exitHardware(radiusDuration, opacityDuration);
|
||||
} else {
|
||||
exitSoftware(radiusDuration, opacityDuration, outerInflection, inflectionOpacity);
|
||||
exitSoftware(radiusDuration, opacityDuration);
|
||||
}
|
||||
}
|
||||
|
||||
private void exitHardware(int radiusDuration, int opacityDuration, int outerInflection,
|
||||
int inflectionOpacity) {
|
||||
private void exitHardware(int radiusDuration, int opacityDuration) {
|
||||
mPendingAnimations.clear();
|
||||
|
||||
final float startX = MathUtils.lerp(
|
||||
mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
|
||||
final float startY = MathUtils.lerp(
|
||||
mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
|
||||
final Paint outerPaint = new Paint();
|
||||
outerPaint.setAntiAlias(true);
|
||||
outerPaint.setColor(mColor);
|
||||
outerPaint.setAlpha((int) (255 * mOuterOpacity + 0.5f));
|
||||
outerPaint.setStyle(Style.FILL);
|
||||
mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
|
||||
mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
|
||||
mPropOuterX = CanvasProperty.createFloat(mOuterX);
|
||||
mPropOuterY = CanvasProperty.createFloat(mOuterY);
|
||||
|
||||
final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
|
||||
final Paint paint = new Paint();
|
||||
final Paint paint = getTempPaint();
|
||||
paint.setAntiAlias(true);
|
||||
paint.setColor(mColor);
|
||||
paint.setAlpha((int) (255 * mOpacity + 0.5f));
|
||||
@@ -442,41 +387,10 @@ class Ripple {
|
||||
RenderNodeAnimator.PAINT_ALPHA, 0);
|
||||
opacityAnim.setDuration(opacityDuration);
|
||||
opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
|
||||
final RenderNodeAnimator outerOpacityAnim;
|
||||
if (outerInflection > 0) {
|
||||
// Outer opacity continues to increase for a bit.
|
||||
outerOpacityAnim = new RenderNodeAnimator(
|
||||
mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
|
||||
outerOpacityAnim.setDuration(outerInflection);
|
||||
outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
|
||||
// Chain the outer opacity exit animation.
|
||||
final int outerDuration = opacityDuration - outerInflection;
|
||||
if (outerDuration > 0) {
|
||||
final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
|
||||
mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
|
||||
outerFadeOutAnim.setDuration(outerDuration);
|
||||
outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
outerFadeOutAnim.setStartDelay(outerInflection);
|
||||
outerFadeOutAnim.setStartValue(inflectionOpacity);
|
||||
outerFadeOutAnim.addListener(mAnimationListener);
|
||||
|
||||
mPendingAnimations.add(outerFadeOutAnim);
|
||||
} else {
|
||||
outerOpacityAnim.addListener(mAnimationListener);
|
||||
}
|
||||
} else {
|
||||
outerOpacityAnim = new RenderNodeAnimator(
|
||||
mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
|
||||
outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
outerOpacityAnim.setDuration(opacityDuration);
|
||||
outerOpacityAnim.addListener(mAnimationListener);
|
||||
}
|
||||
opacityAnim.addListener(mAnimationListener);
|
||||
|
||||
mPendingAnimations.add(radiusAnim);
|
||||
mPendingAnimations.add(opacityAnim);
|
||||
mPendingAnimations.add(outerOpacityAnim);
|
||||
mPendingAnimations.add(xAnim);
|
||||
mPendingAnimations.add(yAnim);
|
||||
|
||||
@@ -485,8 +399,14 @@ class Ripple {
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
private void exitSoftware(int radiusDuration, int opacityDuration, int outerInflection,
|
||||
int inflectionOpacity) {
|
||||
private Paint getTempPaint() {
|
||||
if (mTempPaint == null) {
|
||||
mTempPaint = new Paint();
|
||||
}
|
||||
return mTempPaint;
|
||||
}
|
||||
|
||||
private void exitSoftware(int radiusDuration, int opacityDuration) {
|
||||
final ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
|
||||
radiusAnim.setAutoCancel(true);
|
||||
radiusAnim.setDuration(radiusDuration);
|
||||
@@ -506,58 +426,15 @@ class Ripple {
|
||||
opacityAnim.setAutoCancel(true);
|
||||
opacityAnim.setDuration(opacityDuration);
|
||||
opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
|
||||
final ObjectAnimator outerOpacityAnim;
|
||||
if (outerInflection > 0) {
|
||||
// Outer opacity continues to increase for a bit.
|
||||
outerOpacityAnim = ObjectAnimator.ofFloat(this,
|
||||
"outerOpacity", inflectionOpacity / 255.0f);
|
||||
outerOpacityAnim.setAutoCancel(true);
|
||||
outerOpacityAnim.setDuration(outerInflection);
|
||||
outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
|
||||
// Chain the outer opacity exit animation.
|
||||
final int outerDuration = opacityDuration - outerInflection;
|
||||
if (outerDuration > 0) {
|
||||
outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat(Ripple.this,
|
||||
"outerOpacity", 0);
|
||||
outerFadeOutAnim.setAutoCancel(true);
|
||||
outerFadeOutAnim.setDuration(outerDuration);
|
||||
outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
outerFadeOutAnim.addListener(mAnimationListener);
|
||||
|
||||
mAnimOuterOpacity = outerFadeOutAnim;
|
||||
|
||||
outerFadeOutAnim.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
animation.removeListener(this);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
outerOpacityAnim.addListener(mAnimationListener);
|
||||
}
|
||||
} else {
|
||||
outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
|
||||
outerOpacityAnim.setAutoCancel(true);
|
||||
outerOpacityAnim.setDuration(opacityDuration);
|
||||
outerOpacityAnim.addListener(mAnimationListener);
|
||||
}
|
||||
opacityAnim.addListener(mAnimationListener);
|
||||
|
||||
mAnimRadius = radiusAnim;
|
||||
mAnimOpacity = opacityAnim;
|
||||
mAnimOuterOpacity = outerOpacityAnim;
|
||||
mAnimX = opacityAnim;
|
||||
mAnimY = opacityAnim;
|
||||
mAnimX = xAnim;
|
||||
mAnimY = yAnim;
|
||||
|
||||
radiusAnim.start();
|
||||
opacityAnim.start();
|
||||
outerOpacityAnim.start();
|
||||
xAnim.start();
|
||||
yAnim.start();
|
||||
}
|
||||
@@ -579,10 +456,6 @@ class Ripple {
|
||||
mAnimOpacity.cancel();
|
||||
}
|
||||
|
||||
if (mAnimOuterOpacity != null) {
|
||||
mAnimOuterOpacity.cancel();
|
||||
}
|
||||
|
||||
if (mAnimX != null) {
|
||||
mAnimX.cancel();
|
||||
}
|
||||
@@ -597,7 +470,7 @@ class Ripple {
|
||||
*/
|
||||
private void cancelHardwareAnimations() {
|
||||
final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
|
||||
final int N = runningAnimations == null ? 0 : runningAnimations.size();
|
||||
final int N = runningAnimations.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
runningAnimations.get(i).cancel();
|
||||
}
|
||||
|
||||
535
graphics/java/android/graphics/drawable/RippleBackground.java
Normal file
535
graphics/java/android/graphics/drawable/RippleBackground.java
Normal file
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.graphics.drawable;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.CanvasProperty;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.Rect;
|
||||
import android.util.MathUtils;
|
||||
import android.view.HardwareCanvas;
|
||||
import android.view.RenderNodeAnimator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Draws a Material ripple.
|
||||
*/
|
||||
class RippleBackground {
|
||||
private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
|
||||
private static final TimeInterpolator DECEL_INTERPOLATOR = new LogInterpolator();
|
||||
|
||||
private static final float GLOBAL_SPEED = 1.0f;
|
||||
private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024.0f * GLOBAL_SPEED;
|
||||
private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED;
|
||||
private static final float WAVE_OUTER_OPACITY_VELOCITY_MAX = 4.5f * GLOBAL_SPEED;
|
||||
private static final float WAVE_OUTER_OPACITY_VELOCITY_MIN = 1.5f * GLOBAL_SPEED;
|
||||
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>();
|
||||
private final ArrayList<RenderNodeAnimator> mPendingAnimations =
|
||||
new ArrayList<RenderNodeAnimator>();
|
||||
|
||||
private final RippleDrawable mOwner;
|
||||
|
||||
/** Bounds used for computing max radius. */
|
||||
private final Rect mBounds;
|
||||
|
||||
/** Full-opacity color for drawing this ripple. */
|
||||
private int mColor;
|
||||
|
||||
/** Maximum ripple radius. */
|
||||
private float mOuterRadius;
|
||||
|
||||
/** Screen density used to adjust pixel-based velocities. */
|
||||
private float mDensity;
|
||||
|
||||
private float mStartingX;
|
||||
private float mStartingY;
|
||||
private float mClampedStartingX;
|
||||
private float mClampedStartingY;
|
||||
|
||||
// Hardware rendering properties.
|
||||
private CanvasProperty<Paint> mPropOuterPaint;
|
||||
private CanvasProperty<Float> mPropOuterRadius;
|
||||
private CanvasProperty<Float> mPropOuterX;
|
||||
private CanvasProperty<Float> mPropOuterY;
|
||||
|
||||
// Software animators.
|
||||
private ObjectAnimator mAnimOuterOpacity;
|
||||
private ObjectAnimator mAnimX;
|
||||
private ObjectAnimator mAnimY;
|
||||
|
||||
// Temporary paint used for creating canvas properties.
|
||||
private Paint mTempPaint;
|
||||
|
||||
// Software rendering properties.
|
||||
private float mOuterOpacity = 0;
|
||||
private float mOuterX;
|
||||
private float mOuterY;
|
||||
|
||||
// Values used to tween between the start and end positions.
|
||||
private float mTweenX = 0;
|
||||
private float mTweenY = 0;
|
||||
|
||||
/** Whether we should be drawing hardware animations. */
|
||||
private boolean mHardwareAnimating;
|
||||
|
||||
/** Whether we can use hardware acceleration for the exit animation. */
|
||||
private boolean mCanUseHardware;
|
||||
|
||||
/** Whether we have an explicit maximum radius. */
|
||||
private boolean mHasMaxRadius;
|
||||
|
||||
/**
|
||||
* Creates a new ripple.
|
||||
*/
|
||||
public RippleBackground(RippleDrawable owner, Rect bounds, float startingX, float startingY) {
|
||||
mOwner = owner;
|
||||
mBounds = bounds;
|
||||
|
||||
mStartingX = startingX;
|
||||
mStartingY = startingY;
|
||||
}
|
||||
|
||||
public void setup(int maxRadius, int color, float density) {
|
||||
mColor = color | 0xFF000000;
|
||||
|
||||
if (maxRadius != RippleDrawable.RADIUS_AUTO) {
|
||||
mHasMaxRadius = true;
|
||||
mOuterRadius = maxRadius;
|
||||
} else {
|
||||
final float halfWidth = mBounds.width() / 2.0f;
|
||||
final float halfHeight = mBounds.height() / 2.0f;
|
||||
mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
|
||||
}
|
||||
|
||||
mOuterX = 0;
|
||||
mOuterY = 0;
|
||||
mDensity = density;
|
||||
|
||||
clampStartingPosition();
|
||||
}
|
||||
|
||||
private void clampStartingPosition() {
|
||||
final float cX = mBounds.exactCenterX();
|
||||
final float cY = mBounds.exactCenterY();
|
||||
final float dX = mStartingX - cX;
|
||||
final float dY = mStartingY - cY;
|
||||
final float r = mOuterRadius;
|
||||
if (dX * dX + dY * dY > r * r) {
|
||||
// Point is outside the circle, clamp to the circumference.
|
||||
final double angle = Math.atan2(dY, dX);
|
||||
mClampedStartingX = cX + (float) (Math.cos(angle) * r);
|
||||
mClampedStartingY = cY + (float) (Math.sin(angle) * r);
|
||||
} else {
|
||||
mClampedStartingX = mStartingX;
|
||||
mClampedStartingY = mStartingY;
|
||||
}
|
||||
}
|
||||
|
||||
public void onHotspotBoundsChanged() {
|
||||
if (!mHasMaxRadius) {
|
||||
final float halfWidth = mBounds.width() / 2.0f;
|
||||
final float halfHeight = mBounds.height() / 2.0f;
|
||||
mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
|
||||
|
||||
clampStartingPosition();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setOuterOpacity(float a) {
|
||||
mOuterOpacity = a;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public float getOuterOpacity() {
|
||||
return mOuterOpacity;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setXGravity(float x) {
|
||||
mTweenX = x;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public float getXGravity() {
|
||||
return mTweenX;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setYGravity(float y) {
|
||||
mTweenY = y;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public float getYGravity() {
|
||||
return mTweenY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the ripple centered at (0,0) using the specified paint.
|
||||
*/
|
||||
public boolean draw(Canvas c, Paint p) {
|
||||
final boolean canUseHardware = c.isHardwareAccelerated();
|
||||
if (mCanUseHardware != canUseHardware && mCanUseHardware) {
|
||||
// We've switched from hardware to non-hardware mode. Panic.
|
||||
cancelHardwareAnimations();
|
||||
}
|
||||
mCanUseHardware = canUseHardware;
|
||||
|
||||
final boolean hasContent;
|
||||
if (canUseHardware && mHardwareAnimating) {
|
||||
hasContent = drawHardware((HardwareCanvas) c);
|
||||
} else {
|
||||
hasContent = drawSoftware(c, p);
|
||||
}
|
||||
|
||||
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) {
|
||||
cancelHardwareAnimations();
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
pendingAnimations.get(i).setTarget(c);
|
||||
pendingAnimations.get(i).start();
|
||||
}
|
||||
|
||||
mRunningAnimations.addAll(pendingAnimations);
|
||||
pendingAnimations.clear();
|
||||
}
|
||||
|
||||
c.drawCircle(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
if (outerAlpha > 0 && mOuterRadius > 0) {
|
||||
p.setAlpha(outerAlpha);
|
||||
p.setStyle(Style.FILL);
|
||||
c.drawCircle(mOuterX, mOuterY, mOuterRadius, p);
|
||||
hasContent = true;
|
||||
}
|
||||
|
||||
p.setAlpha(paintAlpha);
|
||||
|
||||
return hasContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum bounds of the ripple relative to the ripple center.
|
||||
*/
|
||||
public void getBounds(Rect bounds) {
|
||||
final int outerX = (int) mOuterX;
|
||||
final int outerY = (int) mOuterY;
|
||||
final int r = (int) mOuterRadius;
|
||||
bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the starting position relative to the drawable bounds. No-op if
|
||||
* the ripple has already entered.
|
||||
*/
|
||||
public void move(float x, float y) {
|
||||
mStartingX = x;
|
||||
mStartingY = y;
|
||||
|
||||
clampStartingPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the enter animation.
|
||||
*/
|
||||
public void enter() {
|
||||
final int radiusDuration = (int)
|
||||
(1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
|
||||
final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_VELOCITY_MIN);
|
||||
|
||||
final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", 1);
|
||||
cX.setAutoCancel(true);
|
||||
cX.setDuration(radiusDuration);
|
||||
cX.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
cX.setStartDelay(RIPPLE_ENTER_DELAY);
|
||||
|
||||
final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "yGravity", 1);
|
||||
cY.setAutoCancel(true);
|
||||
cY.setDuration(radiusDuration);
|
||||
cY.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
cY.setStartDelay(RIPPLE_ENTER_DELAY);
|
||||
|
||||
final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
|
||||
outer.setAutoCancel(true);
|
||||
outer.setDuration(outerDuration);
|
||||
outer.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
|
||||
mAnimOuterOpacity = outer;
|
||||
mAnimX = cX;
|
||||
mAnimY = cY;
|
||||
|
||||
// Enter animations always run on the UI thread, since it's unlikely
|
||||
// that anything interesting is happening until the user lifts their
|
||||
// finger.
|
||||
outer.start();
|
||||
cX.start();
|
||||
cY.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the exit animation.
|
||||
*/
|
||||
public void exit() {
|
||||
cancelSoftwareAnimations();
|
||||
|
||||
// Scale the outer max opacity and opacity velocity based
|
||||
// on the size of the outer radius.
|
||||
final int opacityDuration = (int) (1000 / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
|
||||
final float outerSizeInfluence = MathUtils.constrain(
|
||||
(mOuterRadius - WAVE_OUTER_SIZE_INFLUENCE_MIN * mDensity)
|
||||
/ (WAVE_OUTER_SIZE_INFLUENCE_MAX * mDensity), 0, 1);
|
||||
final float outerOpacityVelocity = MathUtils.lerp(WAVE_OUTER_OPACITY_VELOCITY_MIN,
|
||||
WAVE_OUTER_OPACITY_VELOCITY_MAX, outerSizeInfluence);
|
||||
|
||||
// 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)
|
||||
/ (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
|
||||
final int inflectionOpacity = (int) (255 * (mOuterOpacity + outerInflection
|
||||
* outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
|
||||
|
||||
if (mCanUseHardware) {
|
||||
exitHardware(opacityDuration, outerInflection, inflectionOpacity);
|
||||
} else {
|
||||
exitSoftware(opacityDuration, outerInflection, inflectionOpacity);
|
||||
}
|
||||
}
|
||||
|
||||
private void exitHardware(int opacityDuration, int outerInflection, int inflectionOpacity) {
|
||||
mPendingAnimations.clear();
|
||||
|
||||
// TODO: Adjust background by starting position.
|
||||
final float startX = MathUtils.lerp(
|
||||
mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
|
||||
final float startY = MathUtils.lerp(
|
||||
mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
|
||||
|
||||
final Paint outerPaint = getTempPaint();
|
||||
outerPaint.setAntiAlias(true);
|
||||
outerPaint.setColor(mColor);
|
||||
outerPaint.setAlpha((int) (255 * mOuterOpacity + 0.5f));
|
||||
outerPaint.setStyle(Style.FILL);
|
||||
mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
|
||||
mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
|
||||
mPropOuterX = CanvasProperty.createFloat(mOuterX);
|
||||
mPropOuterY = CanvasProperty.createFloat(mOuterY);
|
||||
|
||||
final RenderNodeAnimator outerOpacityAnim;
|
||||
if (outerInflection > 0) {
|
||||
// Outer opacity continues to increase for a bit.
|
||||
outerOpacityAnim = new RenderNodeAnimator(
|
||||
mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
|
||||
outerOpacityAnim.setDuration(outerInflection);
|
||||
outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
|
||||
// Chain the outer opacity exit animation.
|
||||
final int outerDuration = opacityDuration - outerInflection;
|
||||
if (outerDuration > 0) {
|
||||
final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
|
||||
mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
|
||||
outerFadeOutAnim.setDuration(outerDuration);
|
||||
outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
outerFadeOutAnim.setStartDelay(outerInflection);
|
||||
outerFadeOutAnim.setStartValue(inflectionOpacity);
|
||||
outerFadeOutAnim.addListener(mAnimationListener);
|
||||
|
||||
mPendingAnimations.add(outerFadeOutAnim);
|
||||
} else {
|
||||
outerOpacityAnim.addListener(mAnimationListener);
|
||||
}
|
||||
} else {
|
||||
outerOpacityAnim = new RenderNodeAnimator(
|
||||
mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
|
||||
outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
outerOpacityAnim.setDuration(opacityDuration);
|
||||
outerOpacityAnim.addListener(mAnimationListener);
|
||||
}
|
||||
|
||||
mPendingAnimations.add(outerOpacityAnim);
|
||||
|
||||
mHardwareAnimating = true;
|
||||
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
private Paint getTempPaint() {
|
||||
if (mTempPaint == null) {
|
||||
mTempPaint = new Paint();
|
||||
}
|
||||
return mTempPaint;
|
||||
}
|
||||
|
||||
private void exitSoftware(int opacityDuration, int outerInflection, int inflectionOpacity) {
|
||||
final ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", 1);
|
||||
xAnim.setAutoCancel(true);
|
||||
xAnim.setDuration(opacityDuration);
|
||||
xAnim.setInterpolator(DECEL_INTERPOLATOR);
|
||||
|
||||
final ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", 1);
|
||||
yAnim.setAutoCancel(true);
|
||||
yAnim.setDuration(opacityDuration);
|
||||
yAnim.setInterpolator(DECEL_INTERPOLATOR);
|
||||
|
||||
final ObjectAnimator outerOpacityAnim;
|
||||
if (outerInflection > 0) {
|
||||
// Outer opacity continues to increase for a bit.
|
||||
outerOpacityAnim = ObjectAnimator.ofFloat(this,
|
||||
"outerOpacity", inflectionOpacity / 255.0f);
|
||||
outerOpacityAnim.setAutoCancel(true);
|
||||
outerOpacityAnim.setDuration(outerInflection);
|
||||
outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
|
||||
// Chain the outer opacity exit animation.
|
||||
final int outerDuration = opacityDuration - outerInflection;
|
||||
if (outerDuration > 0) {
|
||||
outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat(
|
||||
RippleBackground.this, "outerOpacity", 0);
|
||||
outerFadeOutAnim.setAutoCancel(true);
|
||||
outerFadeOutAnim.setDuration(outerDuration);
|
||||
outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
outerFadeOutAnim.addListener(mAnimationListener);
|
||||
|
||||
mAnimOuterOpacity = outerFadeOutAnim;
|
||||
|
||||
outerFadeOutAnim.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
animation.removeListener(this);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
outerOpacityAnim.addListener(mAnimationListener);
|
||||
}
|
||||
} else {
|
||||
outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
|
||||
outerOpacityAnim.setAutoCancel(true);
|
||||
outerOpacityAnim.setDuration(opacityDuration);
|
||||
outerOpacityAnim.addListener(mAnimationListener);
|
||||
}
|
||||
|
||||
mAnimOuterOpacity = outerOpacityAnim;
|
||||
mAnimX = xAnim;
|
||||
mAnimY = yAnim;
|
||||
|
||||
outerOpacityAnim.start();
|
||||
xAnim.start();
|
||||
yAnim.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all animations.
|
||||
*/
|
||||
public void cancel() {
|
||||
cancelSoftwareAnimations();
|
||||
cancelHardwareAnimations();
|
||||
}
|
||||
|
||||
private void cancelSoftwareAnimations() {
|
||||
if (mAnimOuterOpacity != null) {
|
||||
mAnimOuterOpacity.cancel();
|
||||
}
|
||||
|
||||
if (mAnimX != null) {
|
||||
mAnimX.cancel();
|
||||
}
|
||||
|
||||
if (mAnimY != null) {
|
||||
mAnimY.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels any running hardware animations.
|
||||
*/
|
||||
private void cancelHardwareAnimations() {
|
||||
final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
|
||||
final int N = runningAnimations.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
runningAnimations.get(i).cancel();
|
||||
}
|
||||
|
||||
runningAnimations.clear();
|
||||
}
|
||||
|
||||
private void removeSelf() {
|
||||
// The owner will invalidate itself.
|
||||
mOwner.removeBackground(this);
|
||||
}
|
||||
|
||||
private void invalidateSelf() {
|
||||
mOwner.invalidateSelf();
|
||||
}
|
||||
|
||||
private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
removeSelf();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,8 +120,22 @@ public class RippleDrawable extends LayerDrawable {
|
||||
/** The masking layer, e.g. the layer with id R.id.mask. */
|
||||
private Drawable mMask;
|
||||
|
||||
/** The current hotspot. May be actively animating or pending entry. */
|
||||
private Ripple mHotspot;
|
||||
/** The current background. May be actively animating or pending entry. */
|
||||
private RippleBackground mBackground;
|
||||
|
||||
/** Whether we expect to draw a background when visible. */
|
||||
private boolean mBackgroundActive;
|
||||
|
||||
/** The current ripple. May be actively animating or pending entry. */
|
||||
private Ripple mRipple;
|
||||
|
||||
/** Whether we expect to draw a ripple when visible. */
|
||||
private boolean mRippleActive;
|
||||
|
||||
// Hotspot coordinates that are awaiting activation.
|
||||
private float mPendingX;
|
||||
private float mPendingY;
|
||||
private boolean mHasPending;
|
||||
|
||||
/**
|
||||
* Lazily-created array of actively animating ripples. Inactive ripples are
|
||||
@@ -142,9 +156,6 @@ public class RippleDrawable extends LayerDrawable {
|
||||
/** Whether bounds are being overridden. */
|
||||
private boolean mOverrideBounds;
|
||||
|
||||
/** Whether the hotspot is currently active (e.g. focused or pressed). */
|
||||
private boolean mActive;
|
||||
|
||||
/**
|
||||
* Constructor used for drawable inflation.
|
||||
*/
|
||||
@@ -205,20 +216,26 @@ public class RippleDrawable extends LayerDrawable {
|
||||
protected boolean onStateChange(int[] stateSet) {
|
||||
super.onStateChange(stateSet);
|
||||
|
||||
// TODO: This would make more sense in a StateListDrawable.
|
||||
boolean active = false;
|
||||
boolean enabled = false;
|
||||
boolean pressed = false;
|
||||
boolean focused = false;
|
||||
|
||||
final int N = stateSet.length;
|
||||
for (int i = 0; i < N; i++) {
|
||||
if (stateSet[i] == R.attr.state_enabled) {
|
||||
enabled = true;
|
||||
}
|
||||
if (stateSet[i] == R.attr.state_focused
|
||||
|| stateSet[i] == R.attr.state_pressed) {
|
||||
active = true;
|
||||
|| stateSet[i] == R.attr.state_selected) {
|
||||
focused = true;
|
||||
}
|
||||
if (stateSet[i] == R.attr.state_pressed) {
|
||||
pressed = true;
|
||||
}
|
||||
}
|
||||
setActive(active && enabled);
|
||||
|
||||
setRippleActive(enabled && pressed);
|
||||
setBackgroundActive(focused || (enabled && pressed));
|
||||
|
||||
// Update the paint color. Only applicable when animated in software.
|
||||
if (mRipplePaint != null && mState.mColor != null) {
|
||||
@@ -235,14 +252,24 @@ public class RippleDrawable extends LayerDrawable {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setActive(boolean active) {
|
||||
if (mActive != active) {
|
||||
mActive = active;
|
||||
|
||||
private void setRippleActive(boolean active) {
|
||||
if (mRippleActive != active) {
|
||||
mRippleActive = active;
|
||||
if (active) {
|
||||
activateHotspot();
|
||||
activateRipple();
|
||||
} else {
|
||||
removeHotspot();
|
||||
removeRipple();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setBackgroundActive(boolean active) {
|
||||
if (mBackgroundActive != active) {
|
||||
mBackgroundActive = active;
|
||||
if (active) {
|
||||
activateBackground();
|
||||
} else {
|
||||
removeBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,11 +288,23 @@ public class RippleDrawable extends LayerDrawable {
|
||||
|
||||
@Override
|
||||
public boolean setVisible(boolean visible, boolean restart) {
|
||||
final boolean changed = super.setVisible(visible, restart);
|
||||
|
||||
if (!visible) {
|
||||
clearHotspots();
|
||||
} else if (changed) {
|
||||
// If we just became visible, ensure the background and ripple
|
||||
// visibilities are consistent with their internal states.
|
||||
if (mRippleActive) {
|
||||
activateRipple();
|
||||
}
|
||||
|
||||
if (mBackgroundActive) {
|
||||
activateBackground();
|
||||
}
|
||||
}
|
||||
|
||||
return super.setVisible(visible, restart);
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,54 +438,101 @@ public class RippleDrawable extends LayerDrawable {
|
||||
|
||||
@Override
|
||||
public void setHotspot(float x, float y) {
|
||||
if (mHotspot == null) {
|
||||
mHotspot = new Ripple(this, mHotspotBounds, x, y);
|
||||
if (mRipple == null || mBackground == null) {
|
||||
mPendingX = x;
|
||||
mPendingY = y;
|
||||
mHasPending = true;
|
||||
}
|
||||
|
||||
if (mActive) {
|
||||
activateHotspot();
|
||||
}
|
||||
} else {
|
||||
mHotspot.move(x, y);
|
||||
if (mRipple != null) {
|
||||
mRipple.move(x, y);
|
||||
}
|
||||
|
||||
if (mBackground != null) {
|
||||
mBackground.move(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an active hotspot at the specified location.
|
||||
*/
|
||||
private void activateHotspot() {
|
||||
private void activateBackground() {
|
||||
if (mBackground == null) {
|
||||
final float x;
|
||||
final float y;
|
||||
if (mHasPending) {
|
||||
mHasPending = false;
|
||||
x = mPendingX;
|
||||
y = mPendingY;
|
||||
} else {
|
||||
x = mHotspotBounds.exactCenterX();
|
||||
y = mHotspotBounds.exactCenterY();
|
||||
}
|
||||
mBackground = new RippleBackground(this, mHotspotBounds, x, y);
|
||||
}
|
||||
|
||||
final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
|
||||
mBackground.setup(mState.mMaxRadius, color, mDensity);
|
||||
mBackground.enter();
|
||||
}
|
||||
|
||||
private void removeBackground() {
|
||||
if (mBackground != null) {
|
||||
// Don't null out the background, we need it to draw!
|
||||
mBackground.exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an active hotspot at the specified location.
|
||||
*/
|
||||
private void activateRipple() {
|
||||
if (mAnimatingRipplesCount >= MAX_RIPPLES) {
|
||||
// This should never happen unless the user is tapping like a maniac
|
||||
// or there is a bug that's preventing ripples from being removed.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mHotspot == null) {
|
||||
final float x = mHotspotBounds.exactCenterX();
|
||||
final float y = mHotspotBounds.exactCenterY();
|
||||
mHotspot = new Ripple(this, mHotspotBounds, x, y);
|
||||
if (mRipple == null) {
|
||||
final float x;
|
||||
final float y;
|
||||
if (mHasPending) {
|
||||
mHasPending = false;
|
||||
x = mPendingX;
|
||||
y = mPendingY;
|
||||
} else {
|
||||
x = mHotspotBounds.exactCenterX();
|
||||
y = mHotspotBounds.exactCenterY();
|
||||
}
|
||||
mRipple = new Ripple(this, mHotspotBounds, x, y);
|
||||
}
|
||||
|
||||
final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
|
||||
mHotspot.setup(mState.mMaxRadius, color, mDensity);
|
||||
mHotspot.enter();
|
||||
mRipple.setup(mState.mMaxRadius, color, mDensity);
|
||||
mRipple.enter();
|
||||
|
||||
if (mAnimatingRipples == null) {
|
||||
mAnimatingRipples = new Ripple[MAX_RIPPLES];
|
||||
}
|
||||
mAnimatingRipples[mAnimatingRipplesCount++] = mHotspot;
|
||||
mAnimatingRipples[mAnimatingRipplesCount++] = mRipple;
|
||||
}
|
||||
|
||||
private void removeHotspot() {
|
||||
if (mHotspot != null) {
|
||||
mHotspot.exit();
|
||||
mHotspot = null;
|
||||
private void removeRipple() {
|
||||
if (mRipple != null) {
|
||||
mRipple.exit();
|
||||
mRipple = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearHotspots() {
|
||||
if (mHotspot != null) {
|
||||
mHotspot.cancel();
|
||||
mHotspot = null;
|
||||
if (mRipple != null) {
|
||||
mRipple.cancel();
|
||||
mRipple = null;
|
||||
}
|
||||
|
||||
if (mBackground != null) {
|
||||
mBackground.cancel();
|
||||
mBackground = null;
|
||||
}
|
||||
|
||||
final int count = mAnimatingRipplesCount;
|
||||
@@ -486,6 +572,10 @@ public class RippleDrawable extends LayerDrawable {
|
||||
for (int i = 0; i < count; i++) {
|
||||
ripples[i].onHotspotBoundsChanged();
|
||||
}
|
||||
|
||||
if (mBackground != null) {
|
||||
mBackground.onHotspotBoundsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -524,18 +614,28 @@ public class RippleDrawable extends LayerDrawable {
|
||||
// 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;
|
||||
final int rippleLayer = drawRippleLayer(canvas, bounds, xfermode);
|
||||
|
||||
// If we have a background and a non-opaque mask, draw the masking layer.
|
||||
final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode);
|
||||
if (backgroundLayer >= 0) {
|
||||
if (drawMask) {
|
||||
drawMaskingLayer(canvas, bounds, DST_IN);
|
||||
}
|
||||
canvas.restoreToCount(backgroundLayer);
|
||||
}
|
||||
|
||||
// If we have ripples and a non-opaque mask, draw the masking layer.
|
||||
if (rippleLayer >= 0 && drawMask) {
|
||||
drawMaskingLayer(canvas, bounds, DST_IN);
|
||||
final int rippleLayer = drawRippleLayer(canvas, bounds, xfermode);
|
||||
if (rippleLayer >= 0) {
|
||||
if (drawMask) {
|
||||
drawMaskingLayer(canvas, bounds, DST_IN);
|
||||
}
|
||||
canvas.restoreToCount(rippleLayer);
|
||||
}
|
||||
|
||||
// Composite the layers if needed.
|
||||
if (contentLayer >= 0) {
|
||||
canvas.restoreToCount(contentLayer);
|
||||
} else if (rippleLayer >= 0) {
|
||||
canvas.restoreToCount(rippleLayer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,15 +650,20 @@ public class RippleDrawable extends LayerDrawable {
|
||||
final int count = mAnimatingRipplesCount;
|
||||
final int index = getRippleIndex(ripple);
|
||||
if (index >= 0) {
|
||||
for (int i = index + 1; i < count; i++) {
|
||||
ripples[i - 1] = ripples[i];
|
||||
}
|
||||
System.arraycopy(ripples, index + 1, ripples, index + 1 - 1, count - (index + 1));
|
||||
ripples[count - 1] = null;
|
||||
mAnimatingRipplesCount--;
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
void removeBackground(RippleBackground background) {
|
||||
if (mBackground == background) {
|
||||
mBackground = null;
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
private int getRippleIndex(Ripple ripple) {
|
||||
final Ripple[] ripples = mAnimatingRipples;
|
||||
final int count = mAnimatingRipplesCount;
|
||||
@@ -577,7 +682,7 @@ public class RippleDrawable extends LayerDrawable {
|
||||
// We don't need a layer if we don't expect to draw any ripples, we have
|
||||
// an explicit mask, or if the non-mask content is all opaque.
|
||||
boolean needsLayer = false;
|
||||
if (mAnimatingRipplesCount > 0 && mMask == null) {
|
||||
if ((mAnimatingRipplesCount > 0 || mBackground != null) && mMask == null) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (array[i].mId != R.id.mask
|
||||
&& array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
|
||||
@@ -601,12 +706,62 @@ public class RippleDrawable extends LayerDrawable {
|
||||
return restoreToCount;
|
||||
}
|
||||
|
||||
private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
|
||||
final int count = mAnimatingRipplesCount;
|
||||
if (count == 0) {
|
||||
return -1;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -631,6 +786,7 @@ public class RippleDrawable extends LayerDrawable {
|
||||
int restoreTranslate = -1;
|
||||
|
||||
// Draw ripples and update the animating ripples array.
|
||||
final int count = mAnimatingRipplesCount;
|
||||
final Ripple[] ripples = mAnimatingRipples;
|
||||
for (int i = 0; i < count; i++) {
|
||||
final Ripple ripple = ripples[i];
|
||||
@@ -705,6 +861,13 @@ public class RippleDrawable extends LayerDrawable {
|
||||
drawingBounds.union(rippleBounds);
|
||||
}
|
||||
|
||||
final RippleBackground background = mBackground;
|
||||
if (background != null) {
|
||||
background.getBounds(rippleBounds);
|
||||
rippleBounds.offset(cX, cY);
|
||||
drawingBounds.union(rippleBounds);
|
||||
}
|
||||
|
||||
dirtyBounds.union(drawingBounds);
|
||||
dirtyBounds.union(super.getDirtyBounds());
|
||||
return dirtyBounds;
|
||||
|
||||
Reference in New Issue
Block a user