Support Interpolation of strings when using native driver in Animated, fix Expected node to be marked as "native", optimize AnimatedNode creation and connections (#18187)

Summary:
Allow interpolation of strings with useNativeDriver. This allows animating much more of react-native-svg. This PR adds support for native animation of lengths with units, path data, colors etc. Plus, fixing the redundantly created nodes and (and thus, previously incorrect) connection of native animated nodes, improving performance.

Docs will need to change, specifying that string interpolation works with the native driver as well.

[GENERAL] [Added] Add support for native driven string interpolation in Animated
[GENERAL] Fix exception: Expected node to be marked as "native"
[GENERAL] Fix connection of AnimatedNodes and creation of redundant AnimatedNodes
Pull Request resolved: https://github.com/facebook/react-native/pull/18187

Differential Revision: D14597147

Pulled By: cpojer

fbshipit-source-id: 82a948a95419236be7931a8cc4ff72f41e477e9c
This commit is contained in:
Mikael Sand
2019-03-26 09:33:25 -07:00
committed by Facebook Github Bot
parent 81a702b426
commit 5e4a5892b9
13 changed files with 240 additions and 18 deletions
@@ -9,6 +9,12 @@ package com.facebook.react.animated;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
/**
@@ -21,6 +27,7 @@ import javax.annotation.Nullable;
public static final String EXTRAPOLATE_TYPE_IDENTITY = "identity";
public static final String EXTRAPOLATE_TYPE_CLAMP = "clamp";
public static final String EXTRAPOLATE_TYPE_EXTEND = "extend";
static final Pattern regex = Pattern.compile("[0-9.-]+");
private static double[] fromDoubleArray(ReadableArray ary) {
double[] res = new double[ary.size()];
@@ -105,13 +112,68 @@ import javax.annotation.Nullable;
private final double mInputRange[];
private final double mOutputRange[];
private String mPattern;
private double mOutputs[][];
private final boolean mHasStringOutput;
private final Matcher mSOutputMatcher;
private final String mExtrapolateLeft;
private final String mExtrapolateRight;
private @Nullable ValueAnimatedNode mParent;
private boolean mShouldRound;
private int mNumVals;
public InterpolationAnimatedNode(ReadableMap config) {
mInputRange = fromDoubleArray(config.getArray("inputRange"));
mOutputRange = fromDoubleArray(config.getArray("outputRange"));
ReadableArray output = config.getArray("outputRange");
mHasStringOutput = output.getType(0) == ReadableType.String;
if (mHasStringOutput) {
/*
* Supports string shapes by extracting numbers so new values can be computed,
* and recombines those values into new strings of the same shape. Supports
* things like:
*
* rgba(123, 42, 99, 0.36) // colors
* -45deg // values with units
*/
int size = output.size();
mOutputRange = new double[size];
mPattern = output.getString(0);
mShouldRound = mPattern.startsWith("rgb");
mSOutputMatcher = regex.matcher(mPattern);
ArrayList<ArrayList<Double>> mOutputRanges = new ArrayList<>();
for (int i = 0; i < size; i++) {
String val = output.getString(i);
Matcher m = regex.matcher(val);
ArrayList<Double> outputRange = new ArrayList<>();
mOutputRanges.add(outputRange);
while (m.find()) {
Double parsed = Double.parseDouble(m.group());
outputRange.add(parsed);
}
mOutputRange[i] = outputRange.get(0);
}
// ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)']
// ->
// [
// [0, 50],
// [100, 150],
// [200, 250],
// [0, 0.5],
// ]
mNumVals = mOutputRanges.get(0).size();
mOutputs = new double[mNumVals][];
for (int j = 0; j < mNumVals; j++) {
double[] arr = new double[size];
mOutputs[j] = arr;
for (int i = 0; i < size; i++) {
arr[i] = mOutputRanges.get(i).get(j);
}
}
} else {
mOutputRange = fromDoubleArray(output);
mSOutputMatcher = null;
}
mExtrapolateLeft = config.getString("extrapolateLeft");
mExtrapolateRight = config.getString("extrapolateRight");
}
@@ -142,6 +204,33 @@ import javax.annotation.Nullable;
// unattached node.
return;
}
mValue = interpolate(mParent.getValue(), mInputRange, mOutputRange, mExtrapolateLeft, mExtrapolateRight);
double value = mParent.getValue();
mValue = interpolate(value, mInputRange, mOutputRange, mExtrapolateLeft, mExtrapolateRight);
if (mHasStringOutput) {
// 'rgba(0, 100, 200, 0)'
// ->
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
if (mNumVals > 1) {
StringBuffer sb = new StringBuffer(mPattern.length());
int i = 0;
mSOutputMatcher.reset();
while (mSOutputMatcher.find()) {
double val = interpolate(value, mInputRange, mOutputs[i++], mExtrapolateLeft, mExtrapolateRight);
if (mShouldRound) {
// rgba requires that the r,g,b are integers.... so we want to round them, but we *dont* want to
// round the opacity (4th column).
boolean isAlpha = i == 4;
int rounded = (int)Math.round(isAlpha ? val * 1000 : val);
mSOutputMatcher.appendReplacement(sb, isAlpha ? String.valueOf((double)rounded / 1000) : String.valueOf(rounded));
} else {
mSOutputMatcher.appendReplacement(sb, String.valueOf(val));
}
}
mSOutputMatcher.appendTail(sb);
mAnimatedObject = sb.toString();
} else {
mAnimatedObject = mSOutputMatcher.replaceFirst(String.valueOf(mValue));
}
}
}
}
@@ -84,7 +84,12 @@ import javax.annotation.Nullable;
} else if (node instanceof StyleAnimatedNode) {
((StyleAnimatedNode) node).collectViewUpdates(mPropMap);
} else if (node instanceof ValueAnimatedNode) {
mPropMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue());
Object animatedObject = ((ValueAnimatedNode) node).getAnimatedObject();
if (animatedObject instanceof String) {
mPropMap.putString(entry.getKey(), (String)animatedObject);
} else {
mPropMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue());
}
} else {
throw new IllegalArgumentException("Unsupported type of node used in property node " +
node.getClass());
@@ -16,6 +16,7 @@ import javax.annotation.Nullable;
* library.
*/
/*package*/ class ValueAnimatedNode extends AnimatedNode {
/*package*/ Object mAnimatedObject = null;
/*package*/ double mValue = Double.NaN;
/*package*/ double mOffset = 0;
private @Nullable AnimatedNodeValueListener mValueListener;
@@ -33,6 +34,10 @@ import javax.annotation.Nullable;
return mOffset + mValue;
}
public Object getAnimatedObject() {
return mAnimatedObject;
}
public void flattenOffset() {
mValue += mOffset;
mOffset = 0;