summaryrefslogtreecommitdiff
path: root/graphics/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/java/android')
-rw-r--r--graphics/java/android/graphics/FontListParser.java14
-rw-r--r--graphics/java/android/graphics/HardwareRendererObserver.java6
-rw-r--r--graphics/java/android/graphics/RenderNode.java19
-rw-r--r--graphics/java/android/graphics/Typeface.java36
-rw-r--r--graphics/java/android/graphics/drawable/RippleAnimationSession.java133
-rw-r--r--graphics/java/android/graphics/drawable/RippleDrawable.java73
-rw-r--r--graphics/java/android/graphics/drawable/RippleShader.java172
7 files changed, 312 insertions, 141 deletions
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 63df0db99764..95c7715a1688 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -136,7 +136,7 @@ public class FontListParser {
customization.getAdditionalNamedFamilies();
parser.require(XmlPullParser.START_TAG, null, "familyset");
- while (parser.next() != XmlPullParser.END_TAG) {
+ while (keepReading(parser)) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (tag.equals("family")) {
@@ -158,6 +158,12 @@ public class FontListParser {
return new FontConfig(families, aliases, lastModifiedDate, configVersion);
}
+ private static boolean keepReading(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int next = parser.next();
+ return next != XmlPullParser.END_TAG && next != XmlPullParser.END_DOCUMENT;
+ }
+
/**
* Read family tag in fonts.xml or oem_customization.xml
*/
@@ -168,7 +174,7 @@ public class FontListParser {
final String lang = parser.getAttributeValue("", "lang");
final String variant = parser.getAttributeValue(null, "variant");
final List<FontConfig.Font> fonts = new ArrayList<>();
- while (parser.next() != XmlPullParser.END_TAG) {
+ while (keepReading(parser)) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
final String tag = parser.getName();
if (tag.equals(TAG_FONT)) {
@@ -232,7 +238,7 @@ public class FontListParser {
boolean isItalic = STYLE_ITALIC.equals(parser.getAttributeValue(null, ATTR_STYLE));
String fallbackFor = parser.getAttributeValue(null, ATTR_FALLBACK_FOR);
StringBuilder filename = new StringBuilder();
- while (parser.next() != XmlPullParser.END_TAG) {
+ while (keepReading(parser)) {
if (parser.getEventType() == XmlPullParser.TEXT) {
filename.append(parser.getText());
}
@@ -359,6 +365,8 @@ public class FontListParser {
case XmlPullParser.END_TAG:
depth--;
break;
+ case XmlPullParser.END_DOCUMENT:
+ return;
}
}
}
diff --git a/graphics/java/android/graphics/HardwareRendererObserver.java b/graphics/java/android/graphics/HardwareRendererObserver.java
index da9d03c639f7..e2a05723166f 100644
--- a/graphics/java/android/graphics/HardwareRendererObserver.java
+++ b/graphics/java/android/graphics/HardwareRendererObserver.java
@@ -62,7 +62,7 @@ public class HardwareRendererObserver {
* @param handler the Handler to use when invoking callbacks
*/
public HardwareRendererObserver(@NonNull OnFrameMetricsAvailableListener listener,
- @NonNull long[] frameMetrics, @NonNull Handler handler) {
+ @NonNull long[] frameMetrics, @NonNull Handler handler, boolean waitForPresentTime) {
if (handler == null || handler.getLooper() == null) {
throw new NullPointerException("handler and its looper cannot be null");
}
@@ -74,7 +74,7 @@ public class HardwareRendererObserver {
mFrameMetrics = frameMetrics;
mHandler = handler;
mListener = listener;
- mNativePtr = new VirtualRefBasePtr(nCreateObserver());
+ mNativePtr = new VirtualRefBasePtr(nCreateObserver(waitForPresentTime));
}
/*package*/ long getNativeInstance() {
@@ -98,6 +98,6 @@ public class HardwareRendererObserver {
});
}
- private native long nCreateObserver();
+ private native long nCreateObserver(boolean waitForPresentTime);
private static native int nGetNextBuffer(long nativePtr, long[] data);
}
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index f6f770be3de8..da5162b9e15e 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -719,11 +719,11 @@ public final class RenderNode {
/** @hide */
public boolean stretch(float left, float top, float right, float bottom,
float vecX, float vecY, float maxStretchAmount) {
- if (1.0 < vecX || vecX < -1.0) {
- throw new IllegalArgumentException("vecX must be in the range [-1, 1], was " + vecX);
+ if (Float.isInfinite(vecX) || Float.isNaN(vecX)) {
+ throw new IllegalArgumentException("vecX must be a finite, non-NaN value " + vecX);
}
- if (1.0 < vecY || vecY < -1.0) {
- throw new IllegalArgumentException("vecY must be in the range [-1, 1], was " + vecY);
+ if (Float.isInfinite(vecY) || Float.isNaN(vecY)) {
+ throw new IllegalArgumentException("vecY must be a finite, non-NaN value " + vecY);
}
if (top >= bottom || left >= right) {
throw new IllegalArgumentException(
@@ -734,7 +734,16 @@ public final class RenderNode {
throw new IllegalArgumentException(
"The max stretch amount must be >0, got " + maxStretchAmount);
}
- return nStretch(mNativeRenderNode, left, top, right, bottom, vecX, vecY, maxStretchAmount);
+ return nStretch(
+ mNativeRenderNode,
+ left,
+ top,
+ right,
+ bottom,
+ vecX,
+ vecY,
+ maxStretchAmount
+ );
}
/**
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index c80788269c24..2a6bbf36ef76 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -25,6 +25,7 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.annotation.UiThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
@@ -169,6 +170,21 @@ public class Typeface {
Collections.emptyMap();
/**
+ * Returns the shared memory that used for creating Typefaces.
+ *
+ * @return A SharedMemory used for creating Typeface. Maybe null if the lazy initialization is
+ * disabled or inside SystemServer or Zygote.
+ * @hide
+ */
+ @TestApi
+ public static @Nullable SharedMemory getSystemFontMapSharedMemory() {
+ if (ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
+ Objects.requireNonNull(sSystemFontMapSharedMemory);
+ }
+ return sSystemFontMapSharedMemory;
+ }
+
+ /**
* @hide
*/
@UnsupportedAppUsage
@@ -1196,8 +1212,13 @@ public class Typeface {
}
}
- /** @hide */
- public static SharedMemory serializeFontMap(Map<String, Typeface> fontMap)
+ /**
+ * Create a serialized system font mappings.
+ *
+ * @hide
+ */
+ @TestApi
+ public static @NonNull SharedMemory serializeFontMap(@NonNull Map<String, Typeface> fontMap)
throws IOException, ErrnoException {
long[] nativePtrs = new long[fontMap.size()];
// The name table will not be large, so let's create a byte array in memory.
@@ -1229,9 +1250,14 @@ public class Typeface {
}
// buffer's byte order should be BIG_ENDIAN.
- /** @hide */
- @VisibleForTesting
- public static Map<String, Typeface> deserializeFontMap(ByteBuffer buffer) throws IOException {
+ /**
+ * Deserialize the font mapping from the serialized byte buffer.
+ *
+ * @hide
+ */
+ @TestApi
+ public static @NonNull Map<String, Typeface> deserializeFontMap(@NonNull ByteBuffer buffer)
+ throws IOException {
Map<String, Typeface> fontMap = new ArrayMap<>();
int typefacesBytesCount = buffer.getInt();
long[] nativePtrs = nativeReadTypefaces(buffer.slice());
diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
index 80f65f919fa6..f3bf63bb3660 100644
--- a/graphics/java/android/graphics/drawable/RippleAnimationSession.java
+++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
@@ -27,8 +27,9 @@ import android.graphics.Paint;
import android.graphics.RecordingCanvas;
import android.graphics.animation.RenderNodeAnimator;
import android.util.ArraySet;
-import android.view.animation.DecelerateInterpolator;
+import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
import java.util.function.Consumer;
@@ -36,32 +37,41 @@ import java.util.function.Consumer;
* @hide
*/
public final class RippleAnimationSession {
- private static final int ENTER_ANIM_DURATION = 350;
- private static final int EXIT_ANIM_OFFSET = ENTER_ANIM_DURATION;
- private static final int EXIT_ANIM_DURATION = 350;
+ private static final String TAG = "RippleAnimationSession";
+ private static final int ENTER_ANIM_DURATION = 300;
+ private static final int SLIDE_ANIM_DURATION = 450;
+ private static final int EXIT_ANIM_DURATION = 300;
private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
- // Matches R.interpolator.fast_out_slow_in but as we have no context we can't just import that
- private static final TimeInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
-
+ private static final TimeInterpolator PATH_INTERPOLATOR =
+ new PathInterpolator(.2f, 0, 0, 1f);
private Consumer<RippleAnimationSession> mOnSessionEnd;
- private AnimationProperties<Float, Paint> mProperties;
+ private final AnimationProperties<Float, Paint> mProperties;
private AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>> mCanvasProperties;
private Runnable mOnUpdate;
private long mStartTime;
private boolean mForceSoftware;
- private ArraySet<Animator> mActiveAnimations = new ArraySet(3);
+ private final float mWidth, mHeight;
+ private final ValueAnimator mSparkle = ValueAnimator.ofFloat(0, 1);
+ private final ArraySet<Animator> mActiveAnimations = new ArraySet<>(3);
RippleAnimationSession(@NonNull AnimationProperties<Float, Paint> properties,
- boolean forceSoftware) {
+ boolean forceSoftware, float width, float height) {
mProperties = properties;
mForceSoftware = forceSoftware;
- }
-
- void end() {
- for (Animator anim: mActiveAnimations) {
- if (anim != null) anim.end();
- }
- mActiveAnimations.clear();
+ mWidth = width;
+ mHeight = height;
+
+ mSparkle.addUpdateListener(anim -> {
+ final long now = AnimationUtils.currentAnimationTimeMillis();
+ final long elapsed = now - mStartTime - ENTER_ANIM_DURATION;
+ final float phase = (float) elapsed / 1000f;
+ mProperties.getShader().setSecondsOffset(phase);
+ notifyUpdate();
+ });
+ mSparkle.setDuration(ENTER_ANIM_DURATION);
+ mSparkle.setStartDelay(ENTER_ANIM_DURATION);
+ mSparkle.setInterpolator(LINEAR_INTERPOLATOR);
+ mSparkle.setRepeatCount(ValueAnimator.INFINITE);
}
@NonNull RippleAnimationSession enter(Canvas canvas) {
@@ -70,17 +80,19 @@ public final class RippleAnimationSession {
} else {
enterSoftware();
}
- mStartTime = System.nanoTime();
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
return this;
}
@NonNull RippleAnimationSession exit(Canvas canvas) {
+ mSparkle.end();
if (isHwAccelerated(canvas)) exitHardware((RecordingCanvas) canvas);
else exitSoftware();
return this;
}
private void onAnimationEnd(Animator anim) {
+ notifyUpdate();
mActiveAnimations.remove(anim);
}
@@ -92,7 +104,6 @@ public final class RippleAnimationSession {
RippleAnimationSession setOnAnimationUpdated(@Nullable Runnable run) {
mOnUpdate = run;
- mProperties.setOnChange(mOnUpdate);
return this;
}
@@ -122,14 +133,12 @@ public final class RippleAnimationSession {
}
private long computeDelay() {
- long currentTime = System.nanoTime();
- long timePassed = (currentTime - mStartTime) / 1_000_000;
- long difference = EXIT_ANIM_OFFSET;
- return Math.max(difference - timePassed, 0);
+ final long timePassed = AnimationUtils.currentAnimationTimeMillis() - mStartTime;
+ return Math.max((long) SLIDE_ANIM_DURATION - timePassed, 0);
}
+
private void notifyUpdate() {
- Runnable onUpdate = mOnUpdate;
- if (onUpdate != null) onUpdate.run();
+ if (mOnUpdate != null) mOnUpdate.run();
}
RippleAnimationSession setForceSoftwareAnimation(boolean forceSw) {
@@ -153,7 +162,7 @@ public final class RippleAnimationSession {
}
});
exit.setTarget(canvas);
- exit.setInterpolator(DECELERATE_INTERPOLATOR);
+ exit.setInterpolator(LINEAR_INTERPOLATOR);
long delay = computeDelay();
exit.setStartDelay(delay);
@@ -161,36 +170,67 @@ public final class RippleAnimationSession {
mActiveAnimations.add(exit);
}
- private void enterHardware(RecordingCanvas can) {
+ private void enterHardware(RecordingCanvas canvas) {
AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>>
props = getCanvasProperties();
RenderNodeAnimator expand =
new RenderNodeAnimator(props.getProgress(), .5f);
- expand.setTarget(can);
- expand.setDuration(ENTER_ANIM_DURATION);
- expand.addListener(new AnimatorListener(this));
+ RenderNodeAnimator slideX =
+ new RenderNodeAnimator(props.getX(), mWidth / 2);
+ RenderNodeAnimator slideY =
+ new RenderNodeAnimator(props.getY(), mHeight / 2);
+ expand.setTarget(canvas);
+ slideX.setTarget(canvas);
+ slideY.setTarget(canvas);
+ startAnimation(expand, slideX, slideY);
+ }
+
+ private void startAnimation(Animator expand,
+ Animator slideX, Animator slideY) {
+ expand.setDuration(SLIDE_ANIM_DURATION);
+ slideX.setDuration(SLIDE_ANIM_DURATION);
+ slideY.setDuration(SLIDE_ANIM_DURATION);
+ slideX.addListener(new AnimatorListener(this));
expand.setInterpolator(LINEAR_INTERPOLATOR);
+ slideX.setInterpolator(PATH_INTERPOLATOR);
+ slideY.setInterpolator(PATH_INTERPOLATOR);
expand.start();
+ slideX.start();
+ slideY.start();
+ if (!mSparkle.isRunning()) {
+ mSparkle.start();
+ mActiveAnimations.add(mSparkle);
+ }
mActiveAnimations.add(expand);
+ mActiveAnimations.add(slideX);
+ mActiveAnimations.add(slideY);
}
private void enterSoftware() {
ValueAnimator expand = ValueAnimator.ofFloat(0f, 0.5f);
+ ValueAnimator slideX = ValueAnimator.ofFloat(
+ mProperties.getX(), mWidth / 2);
+ ValueAnimator slideY = ValueAnimator.ofFloat(
+ mProperties.getY(), mHeight / 2);
expand.addUpdateListener(updatedAnimation -> {
notifyUpdate();
mProperties.getShader().setProgress((Float) expand.getAnimatedValue());
});
- expand.addListener(new AnimatorListener(this));
- expand.setInterpolator(LINEAR_INTERPOLATOR);
- expand.start();
- mActiveAnimations.add(expand);
+ slideX.addUpdateListener(anim -> {
+ float x = (float) slideX.getAnimatedValue();
+ float y = (float) slideY.getAnimatedValue();
+ mProperties.setOrigin(x, y);
+ mProperties.getShader().setOrigin(x, y);
+ });
+ startAnimation(expand, slideX, slideY);
}
@NonNull AnimationProperties<Float, Paint> getProperties() {
return mProperties;
}
- @NonNull AnimationProperties getCanvasProperties() {
+ @NonNull
+ AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>> getCanvasProperties() {
if (mCanvasProperties == null) {
mCanvasProperties = new AnimationProperties<>(
CanvasProperty.createFloat(mProperties.getX()),
@@ -209,6 +249,7 @@ public final class RippleAnimationSession {
AnimatorListener(RippleAnimationSession session) {
mSession = session;
}
+
@Override
public void onAnimationStart(Animator animation) {
@@ -231,21 +272,12 @@ public final class RippleAnimationSession {
}
static class AnimationProperties<FloatType, PaintType> {
- private final FloatType mY;
- private FloatType mProgress;
- private FloatType mMaxRadius;
+ private final FloatType mProgress;
+ private final FloatType mMaxRadius;
private final PaintType mPaint;
- private final FloatType mX;
private final RippleShader mShader;
- private Runnable mOnChange;
-
- private void onChange() {
- if (mOnChange != null) mOnChange.run();
- }
-
- private void setOnChange(Runnable onChange) {
- mOnChange = onChange;
- }
+ private FloatType mX;
+ private FloatType mY;
AnimationProperties(FloatType x, FloatType y, FloatType maxRadius,
PaintType paint, FloatType progress, RippleShader shader) {
@@ -261,6 +293,11 @@ public final class RippleAnimationSession {
return mProgress;
}
+ void setOrigin(FloatType x, FloatType y) {
+ mX = x;
+ mY = y;
+ }
+
FloatType getX() {
return mX;
}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 5024875aab3c..d6bbee90d73b 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -48,6 +48,7 @@ import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.Shader;
import android.os.Build;
+import android.os.SystemProperties;
import android.util.AttributeSet;
import android.view.animation.LinearInterpolator;
@@ -109,17 +110,6 @@ import java.util.Arrays;
* </pre>
*
* @attr ref android.R.styleable#RippleDrawable_color
- *
- * To change the ripple style, assign the value of "solid" or "patterned" to the android:rippleStyle
- * attribute.
- *
- * <pre>
- * <code>&lt;!-- A red ripple masked against an opaque rectangle. --/>
- * &lt;ripple android:rippleStyle="patterned">
- * &lt;/ripple></code>
- * </pre>
- *
- * @attr ref android.R.styleable#RippleDrawable_rippleStyle
*/
public class RippleDrawable extends LayerDrawable {
/**
@@ -131,12 +121,14 @@ public class RippleDrawable extends LayerDrawable {
/**
* Ripple style where a solid circle is drawn. This is also the default style
* @see #setRippleStyle(int)
+ * @hide
*/
public static final int STYLE_SOLID = 0;
/**
* Ripple style where a circle shape with a patterned,
* noisy interior expands from the hotspot to the bounds".
* @see #setRippleStyle(int)
+ * @hide
*/
public static final int STYLE_PATTERNED = 1;
@@ -159,6 +151,9 @@ public class RippleDrawable extends LayerDrawable {
/** The maximum number of ripples supported. */
private static final int MAX_RIPPLES = 10;
private static final LinearInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+ /** Temporary flag for teamfood. **/
+ private static final boolean FORCE_PATTERNED_STYLE =
+ SystemProperties.getBoolean("persist.material.patternedripple", false);
private final Rect mTempRect = new Rect();
@@ -361,7 +356,9 @@ public class RippleDrawable extends LayerDrawable {
}
} else {
if (focused || hovered) {
- enterPatternedBackgroundAnimation(focused, hovered);
+ if (!pressed) {
+ enterPatternedBackgroundAnimation(focused, hovered);
+ }
} else {
exitPatternedBackgroundAnimation();
}
@@ -571,7 +568,10 @@ public class RippleDrawable extends LayerDrawable {
mState.mMaxRadius = a.getDimensionPixelSize(
R.styleable.RippleDrawable_radius, mState.mMaxRadius);
- mState.mRippleStyle = a.getInteger(R.styleable.RippleDrawable_rippleStyle, STYLE_SOLID);
+ if (!FORCE_PATTERNED_STYLE) {
+ mState.mRippleStyle = a.getInteger(R.styleable.RippleDrawable_rippleStyle,
+ mState.mRippleStyle);
+ }
}
private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
@@ -812,21 +812,25 @@ public class RippleDrawable extends LayerDrawable {
}
private void drawPatterned(@NonNull Canvas canvas) {
- final Rect bounds = getBounds();
+ final Rect bounds = getDirtyBounds();
final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
boolean useCanvasProps = shouldUseCanvasProps(canvas);
boolean changedHotspotBounds = !bounds.equals(mHotspotBounds);
if (isBounded()) {
- canvas.clipRect(mHotspotBounds);
+ canvas.clipRect(bounds);
}
- float x, y;
+ float x, y, w, h;
if (changedHotspotBounds) {
x = mHotspotBounds.exactCenterX();
y = mHotspotBounds.exactCenterY();
+ w = mHotspotBounds.width();
+ h = mHotspotBounds.height();
useCanvasProps = false;
} else {
x = mPendingX;
y = mPendingY;
+ w = bounds.width();
+ h = bounds.height();
}
boolean shouldAnimate = mRippleActive;
boolean shouldExit = mExitingAnimation;
@@ -837,9 +841,9 @@ public class RippleDrawable extends LayerDrawable {
drawPatternedBackground(canvas);
if (shouldAnimate && mRunningAnimations.size() <= MAX_RIPPLES) {
RippleAnimationSession.AnimationProperties<Float, Paint> properties =
- createAnimationProperties(x, y);
- mRunningAnimations.add(new RippleAnimationSession(properties, !useCanvasProps)
- .setOnAnimationUpdated(useCanvasProps ? null : () -> invalidateSelf(false))
+ createAnimationProperties(x, y, w, h);
+ mRunningAnimations.add(new RippleAnimationSession(properties, !useCanvasProps, w, h)
+ .setOnAnimationUpdated(() -> invalidateSelf(false))
.setOnSessionEnd(session -> {
mRunningAnimations.remove(session);
})
@@ -864,20 +868,8 @@ public class RippleDrawable extends LayerDrawable {
} else {
RippleAnimationSession.AnimationProperties<Float, Paint> p =
s.getProperties();
- float posX, posY;
- if (changedHotspotBounds) {
- posX = x;
- posY = y;
- if (p.getPaint().getShader() instanceof RippleShader) {
- RippleShader shader = (RippleShader) p.getPaint().getShader();
- shader.setOrigin(posX, posY);
- }
- } else {
- posX = p.getX();
- posY = p.getY();
- }
float radius = p.getMaxRadius();
- canvas.drawCircle(posX, posY, radius, p.getPaint());
+ canvas.drawCircle(p.getX(), p.getY(), radius, p.getPaint());
}
}
canvas.restoreToCount(saveCount);
@@ -905,14 +897,13 @@ public class RippleDrawable extends LayerDrawable {
private float computeRadius() {
Rect b = getDirtyBounds();
- float gap = 0;
- float radius = (float) Math.sqrt(b.width() * b.width() + b.height() * b.height()) / 2 + gap;
+ float radius = (float) Math.sqrt(b.width() * b.width() + b.height() * b.height()) / 2;
return radius;
}
@NonNull
private RippleAnimationSession.AnimationProperties<Float, Paint> createAnimationProperties(
- float x, float y) {
+ float x, float y, float w, float h) {
Paint p = new Paint(mRipplePaint);
float radius = mState.mMaxRadius;
RippleAnimationSession.AnimationProperties<Float, Paint> properties;
@@ -920,19 +911,19 @@ public class RippleDrawable extends LayerDrawable {
int color = mMaskColorFilter == null
? mState.mColor.getColorForState(getState(), Color.BLACK)
: mMaskColorFilter.getColor();
- color = color | 0xFF000000;
shader.setColor(color);
shader.setOrigin(x, y);
+ shader.setResolution(w, h);
+ shader.setSecondsOffset(0);
shader.setRadius(radius);
shader.setProgress(.0f);
properties = new RippleAnimationSession.AnimationProperties<>(
x, y, radius, p, 0f,
shader);
if (mMaskShader == null) {
- shader.setHasMask(false);
+ shader.setShader(null);
} else {
shader.setShader(mMaskShader);
- shader.setHasMask(true);
}
p.setShader(shader);
p.setColorFilter(null);
@@ -1160,7 +1151,7 @@ public class RippleDrawable extends LayerDrawable {
// 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.
- int maskColor = color | 0xFF000000;
+ int maskColor = mState.mRippleStyle == STYLE_PATTERNED ? color : color | 0xFF000000;
if (mMaskColorFilter.getColor() != maskColor) {
mMaskColorFilter = new PorterDuffColorFilter(maskColor, mMaskColorFilter.getMode());
}
@@ -1248,6 +1239,7 @@ public class RippleDrawable extends LayerDrawable {
* @see #STYLE_PATTERNED
*
* @param style The style of the ripple
+ * @hide
*/
public void setRippleStyle(@RippleStyle int style) throws IllegalArgumentException {
if (style == STYLE_SOLID || style == STYLE_PATTERNED) {
@@ -1260,6 +1252,7 @@ public class RippleDrawable extends LayerDrawable {
/**
* Get the current ripple style
* @return Ripple style
+ * @hide
*/
public @RippleStyle int getRippleStyle() {
return mState.mRippleStyle;
@@ -1276,7 +1269,7 @@ public class RippleDrawable extends LayerDrawable {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
ColorStateList mColor = ColorStateList.valueOf(Color.MAGENTA);
int mMaxRadius = RADIUS_AUTO;
- int mRippleStyle = STYLE_SOLID;
+ int mRippleStyle = FORCE_PATTERNED_STYLE ? STYLE_PATTERNED : STYLE_SOLID;
public RippleState(LayerState orig, RippleDrawable owner, Resources res) {
super(orig, owner, res);
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
index 500efdd84854..25492779e6e9 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -17,58 +17,153 @@
package android.graphics.drawable;
import android.annotation.ColorInt;
-import android.annotation.NonNull;
import android.graphics.Color;
import android.graphics.RuntimeShader;
import android.graphics.Shader;
final class RippleShader extends RuntimeShader {
- private static final String SHADER = "uniform float2 in_origin;\n"
- + "uniform float in_maxRadius;\n"
+ private static final String SHADER_UNIFORMS = "uniform vec2 in_origin;\n"
+ "uniform float in_progress;\n"
+ + "uniform float in_maxRadius;\n"
+ + "uniform vec2 in_resolution;\n"
+ "uniform float in_hasMask;\n"
- + "uniform float4 in_color;\n"
- + "uniform shader in_shader;\n"
- + "float dist2(float2 p0, float2 pf) { return sqrt((pf.x - p0.x) * (pf.x - p0.x) + "
- + "(pf.y - p0.y) * (pf.y - p0.y)); }\n"
- + "float mod2(float a, float b) { return a - (b * floor(a / b)); }\n"
- + "float rand(float2 src) { return fract(sin(dot(src.xy, float2(12.9898, 78.233))) * "
- + "43758.5453123); }\n"
- + "float4 main(float2 p)\n"
- + "{\n"
- + " float fraction = in_progress;\n"
- + " float2 fragCoord = p;//sk_FragCoord.xy;\n"
- + " float maxDist = in_maxRadius;\n"
- + " float fragDist = dist2(in_origin, fragCoord.xy);\n"
- + " float circleRadius = maxDist * fraction;\n"
- + " float colorVal = (fragDist - circleRadius) / maxDist;\n"
- + " float d = fragDist < circleRadius \n"
- + " ? 1. - abs(colorVal * 3. * smoothstep(0., 1., fraction)) \n"
- + " : 1. - abs(colorVal * 5.);\n"
- + " d = smoothstep(0., 1., d);\n"
- + " float divider = 2.;\n"
- + " float x = floor(fragCoord.x / divider);\n"
- + " float y = floor(fragCoord.y / divider);\n"
- + " float density = .95;\n"
- + " d = rand(float2(x, y)) > density ? d : d * .2;\n"
- + " d = d * rand(float2(fraction, x * y));\n"
- + " float alpha = 1. - pow(fraction, 2.);\n"
- + " if (in_hasMask != 0.) {return sample(in_shader).a * in_color * d * alpha;}\n"
- + " return in_color * d * alpha;\n"
+ + "uniform float in_secondsOffset;\n"
+ + "uniform vec4 in_color;\n"
+ + "uniform shader in_shader;\n";
+ private static final String SHADER_LIB =
+ "float triangleNoise(vec2 n) {\n"
+ + " n = fract(n * vec2(5.3987, 5.4421));\n"
+ + " n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));\n"
+ + " float xy = n.x * n.y;\n"
+ + " return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;\n"
+ + "}"
+ + "const float PI = 3.1415926535897932384626;\n"
+ + "\n"
+ + "float threshold(float v, float l, float h) {\n"
+ + " return step(l, v) * (1.0 - step(h, v));\n"
+ + "}\n"
+ + "\n"
+ + "float sparkles(vec2 uv, float t) {\n"
+ + " float n = triangleNoise(uv);\n"
+ + " float s = 0.0;\n"
+ + " for (float i = 0; i < 4; i += 1) {\n"
+ + " float l = i * 0.25;\n"
+ + " float h = l + 0.025;\n"
+ + " float o = abs(sin(0.1 * PI * (t + i)));\n"
+ + " s += threshold(n + o, l, h);\n"
+ + " }\n"
+ + " return saturate(s);\n"
+ + "}\n"
+ + "\n"
+ + "float softCircle(vec2 uv, vec2 xy, float radius, float blur) {\n"
+ + " float blurHalf = blur * 0.5;\n"
+ + " float d = distance(uv, xy);\n"
+ + " return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);\n"
+ + "}\n"
+ + "\n"
+ + "float softRing(vec2 uv, vec2 xy, float radius, float blur) {\n"
+ + " float thickness = 0.4;\n"
+ + " float circle_outer = softCircle(uv, xy, radius + thickness * 0.5, blur);\n"
+ + " float circle_inner = softCircle(uv, xy, radius - thickness * 0.5, blur);\n"
+ + " return circle_outer - circle_inner;\n"
+ + "}\n"
+ + "\n"
+ + "struct Viewport {\n"
+ + " float aspect;\n"
+ + " vec2 uv;\n"
+ + " vec2 resolution_pixels;\n"
+ + "};\n"
+ + "\n"
+ + "Viewport getViewport(vec2 frag_coord, vec2 resolution_pixels) {\n"
+ + " Viewport v;\n"
+ + " v.aspect = resolution_pixels.y / resolution_pixels.x;\n"
+ + " v.uv = frag_coord / resolution_pixels;\n"
+ + " v.uv.y = (1.0 - v.uv.y) * v.aspect;\n"
+ + " v.resolution_pixels = resolution_pixels;\n"
+ + " return v;\n"
+ + "}\n"
+ + "\n"
+ + "vec2 getTouch(vec2 touch_position_pixels, Viewport viewport) {\n"
+ + " vec2 touch = touch_position_pixels / viewport.resolution_pixels;\n"
+ + " touch.y *= viewport.aspect;\n"
+ + " return touch;\n"
+ + "}\n"
+ + "\n"
+ + "struct Wave {\n"
+ + " float ring;\n"
+ + " float circle;\n"
+ + "};\n"
+ + "\n"
+ + "Wave getWave(Viewport viewport, vec2 touch, float progress) {\n"
+ + " float fade = pow((clamp(progress, 0.8, 1.0)), 8.);\n"
+ + " Wave w;\n"
+ + " w.ring = max(softRing(viewport.uv, touch, progress, 0.45) - fade, 0.);\n"
+ + " w.circle = softCircle(viewport.uv, touch, 2.0 * progress, 0.2) - progress;\n"
+ + " return w;\n"
+ + "}\n"
+ + "\n"
+ + "vec4 getRipple(vec4 color, float loudness, float sparkle, Wave wave) {\n"
+ + " float alpha = wave.ring * sparkle * loudness\n"
+ + " + wave.circle * color.a;\n"
+ + " return vec4(color.rgb, saturate(alpha));\n"
+ + "}\n"
+ + "\n"
+ + "float getRingMask(vec2 frag, vec2 center, float r, float progress) {\n"
+ + " float dist = distance(frag, center);\n"
+ + " float expansion = r * .6;\n"
+ + " r = r * min(1.,progress);\n"
+ + " float minD = max(r - expansion, 0.);\n"
+ + " float maxD = r + expansion;\n"
+ + " if (dist > maxD || dist < minD) return .0;\n"
+ + " return min(maxD - dist, dist - minD) / expansion; \n"
+ + "}\n"
+ + "\n"
+ + "float subProgress(float start, float end, float progress) {\n"
+ + " float sub = clamp(progress, start, end);\n"
+ + " return (sub - start) / (end - start); \n"
+ "}\n";
+ private static final String SHADER_MAIN = "vec4 main(vec2 p) {\n"
+ + " float fadeIn = subProgress(0., 0.175, in_progress);\n"
+ + " float fadeOutNoise = subProgress(0.375, 1., in_progress);\n"
+ + " float fadeOutRipple = subProgress(0.375, 0.75, in_progress);\n"
+ + " Viewport vp = getViewport(p, in_resolution);\n"
+ + " vec2 touch = getTouch(in_origin, vp);\n"
+ + " Wave w = getWave(vp, touch, in_progress * 0.25);\n"
+ + " float ring = getRingMask(p, in_origin, in_maxRadius, fadeIn);\n"
+ + " float alpha = min(fadeIn, 1. - fadeOutNoise);\n"
+ + " float sparkle = sparkles(p, in_progress * 0.25 + in_secondsOffset)\n"
+ + " * ring * alpha;\n"
+ + " vec4 r = getRipple(in_color, 1., sparkle, w);\n"
+ + " float fade = min(fadeIn, 1.-fadeOutRipple);\n"
+ + " vec4 circle = vec4(in_color.rgb, softCircle(p, in_origin, in_maxRadius "
+ + " * fadeIn, 0.2) * fade * in_color.a);\n"
+ + " float mask = in_hasMask == 1. ? sample(in_shader).a > 0. ? 1. : 0. : 1.;\n"
+ + " return mix(circle, vec4(1.), sparkle * mask);\n"
+ + "}";
+ private static final String SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN;
RippleShader() {
super(SHADER, false);
}
- public void setShader(@NonNull Shader s) {
- setInputShader("in_shader", s);
+ public void setShader(Shader shader) {
+ if (shader != null) {
+ setInputShader("in_shader", shader);
+ }
+ setUniform("in_hasMask", shader == null ? 0 : 1);
}
public void setRadius(float radius) {
setUniform("in_maxRadius", radius);
}
+ /**
+ * Continuous offset used as noise phase.
+ */
+ public void setSecondsOffset(float t) {
+ setUniform("in_secondsOffset", t);
+ }
+
public void setOrigin(float x, float y) {
setUniform("in_origin", new float[] {x, y});
}
@@ -77,13 +172,16 @@ final class RippleShader extends RuntimeShader {
setUniform("in_progress", progress);
}
- public void setHasMask(boolean hasMask) {
- setUniform("in_hasMask", hasMask ? 1 : 0);
- }
-
+ /**
+ * Color of the circle that's under the sparkles. Sparkles will always be white.
+ */
public void setColor(@ColorInt int colorIn) {
Color color = Color.valueOf(colorIn);
this.setUniform("in_color", new float[] {color.red(),
color.green(), color.blue(), color.alpha()});
}
+
+ public void setResolution(float w, float h) {
+ setUniform("in_resolution", w, h);
+ }
}