Skip to content

Commit 2fc6888

Browse files
committed
Revert "Back out "[react-native][PR] Support Interpolation of strings when using native driver in Animated, fix Expected node to be marked as "native", optimize AnimatedNode creation and connections""
This reverts commit 95c7db9
1 parent 9ca7989 commit 2fc6888

13 files changed

+240
-18
lines changed

Libraries/Animated/src/NativeAnimatedHelper.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,7 @@ function transformDataType(value: any): number {
271271
const radians = (degrees * Math.PI) / 180.0;
272272
return radians;
273273
} else {
274-
// Assume radians
275-
return parseFloat(value) || 0;
274+
return value;
276275
}
277276
}
278277

Libraries/Animated/src/animations/Animation.js

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class Animation {
5757
}
5858
__startNativeAnimation(animatedValue: AnimatedValue): void {
5959
animatedValue.__makeNative();
60+
animatedValue.__connectAnimatedNodes();
6061
this.__nativeId = NativeAnimatedHelper.generateNewAnimationId();
6162
NativeAnimatedHelper.API.startAnimatingNode(
6263
this.__nativeId,

Libraries/Animated/src/nodes/AnimatedInterpolation.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -242,10 +242,11 @@ function createInterpolationFromStringOutputRange(
242242
// ->
243243
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
244244
return outputRange[0].replace(stringShapeRegex, () => {
245-
const val = +interpolations[i++](input);
246-
const rounded =
247-
shouldRound && i < 4 ? Math.round(val) : Math.round(val * 1000) / 1000;
248-
return String(rounded);
245+
let val = +interpolations[i++](input);
246+
if (shouldRound) {
247+
val = i < 4 ? Math.round(val) : Math.round(val * 1000) / 1000;
248+
}
249+
return String(val);
249250
});
250251
};
251252
}

Libraries/Animated/src/nodes/AnimatedNode.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,18 @@ class AnimatedNode {
3535

3636
/* Methods and props used by native Animated impl */
3737
__isNative: boolean;
38+
__isConnected: boolean;
3839
__nativeTag: ?number;
3940
__makeNative() {
4041
if (!this.__isNative) {
4142
throw new Error('This node cannot be made a "native" animated node');
4243
}
4344
}
45+
__connectAnimatedNodes() {
46+
if (!this.__isNative) {
47+
throw new Error('This node cannot be connected natively');
48+
}
49+
}
4450
__getNativeTag(): ?number {
4551
NativeAnimatedHelper.assertNativeAnimatedModule();
4652
invariant(
@@ -49,11 +55,11 @@ class AnimatedNode {
4955
);
5056
if (this.__nativeTag == null) {
5157
const nativeTag: ?number = NativeAnimatedHelper.generateNewNodeTag();
58+
this.__nativeTag = nativeTag;
5259
NativeAnimatedHelper.API.createAnimatedNode(
5360
nativeTag,
5461
this.__getNativeConfig(),
5562
);
56-
this.__nativeTag = nativeTag;
5763
}
5864
return this.__nativeTag;
5965
}

Libraries/Animated/src/nodes/AnimatedProps.js

+1
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ class AnimatedProps extends AnimatedNode {
151151
for (const propKey in this._props) {
152152
const value = this._props[propKey];
153153
if (value instanceof AnimatedNode) {
154+
value.__makeNative();
154155
propsConfig[propKey] = value.__getNativeTag();
155156
}
156157
}

Libraries/Animated/src/nodes/AnimatedStyle.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ class AnimatedStyle extends AnimatedWithChildren {
108108
const styleConfig = {};
109109
for (const styleKey in this._style) {
110110
if (this._style[styleKey] instanceof AnimatedNode) {
111-
styleConfig[styleKey] = this._style[styleKey].__getNativeTag();
111+
const style = this._style[styleKey];
112+
style.__makeNative();
113+
styleConfig[styleKey] = style.__getNativeTag();
112114
}
113115
// Non-animated styles are set using `setNativeProps`, no need
114116
// to pass those as a part of the node config

Libraries/Animated/src/nodes/AnimatedWithChildren.js

+9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ class AnimatedWithChildren extends AnimatedNode {
2525
this.__isNative = true;
2626
for (const child of this._children) {
2727
child.__makeNative();
28+
}
29+
}
30+
}
31+
32+
__connectAnimatedNodes() {
33+
if (!this.__isConnected) {
34+
this.__isConnected = true;
35+
for (const child of this._children) {
36+
child.__connectAnimatedNodes();
2837
NativeAnimatedHelper.API.connectAnimatedNodes(
2938
this.__getNativeTag(),
3039
child.__getNativeTag(),

Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.m

+103-5
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,89 @@
99

1010
#import "RCTAnimationUtils.h"
1111

12+
static NSRegularExpression *regex;
13+
1214
@implementation RCTInterpolationAnimatedNode
1315
{
1416
__weak RCTValueAnimatedNode *_parentNode;
1517
NSArray<NSNumber *> *_inputRange;
1618
NSArray<NSNumber *> *_outputRange;
19+
NSArray<NSArray<NSNumber *> *> *_outputs;
20+
NSArray<NSString *> *_soutputRange;
1721
NSString *_extrapolateLeft;
1822
NSString *_extrapolateRight;
23+
NSUInteger _numVals;
24+
bool _hasStringOutput;
25+
bool _shouldRound;
26+
NSArray<NSTextCheckingResult*> *_matches;
1927
}
2028

2129
- (instancetype)initWithTag:(NSNumber *)tag
2230
config:(NSDictionary<NSString *, id> *)config
2331
{
32+
static dispatch_once_t onceToken;
33+
dispatch_once(&onceToken, ^{
34+
regex = [NSRegularExpression regularExpressionWithPattern:@"[0-9.-]+" options:NSRegularExpressionCaseInsensitive error:nil];
35+
});
2436
if ((self = [super initWithTag:tag config:config])) {
2537
_inputRange = [config[@"inputRange"] copy];
2638
NSMutableArray *outputRange = [NSMutableArray array];
39+
NSMutableArray *soutputRange = [NSMutableArray array];
40+
NSMutableArray<NSMutableArray<NSNumber *> *> *_outputRanges = [NSMutableArray array];
41+
42+
_hasStringOutput = NO;
2743
for (id value in config[@"outputRange"]) {
2844
if ([value isKindOfClass:[NSNumber class]]) {
2945
[outputRange addObject:value];
46+
} else if ([value isKindOfClass:[NSString class]]) {
47+
/**
48+
* Supports string shapes by extracting numbers so new values can be computed,
49+
* and recombines those values into new strings of the same shape. Supports
50+
* things like:
51+
*
52+
* rgba(123, 42, 99, 0.36) // colors
53+
* -45deg // values with units
54+
*/
55+
NSMutableArray *output = [NSMutableArray array];
56+
[_outputRanges addObject:output];
57+
[soutputRange addObject:value];
58+
59+
_matches = [regex matchesInString:value options:0 range:NSMakeRange(0, [value length])];
60+
for (NSTextCheckingResult *match in _matches) {
61+
NSString* strNumber = [value substringWithRange:match.range];
62+
[output addObject:[NSNumber numberWithDouble:strNumber.doubleValue]];
63+
}
64+
65+
_hasStringOutput = YES;
66+
[outputRange addObject:[output objectAtIndex:0]];
67+
}
68+
}
69+
if (_hasStringOutput) {
70+
// ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)']
71+
// ->
72+
// [
73+
// [0, 50],
74+
// [100, 150],
75+
// [200, 250],
76+
// [0, 0.5],
77+
// ]
78+
_numVals = [_matches count];
79+
NSString *value = [soutputRange objectAtIndex:0];
80+
_shouldRound = [value containsString:@"rgb"];
81+
_matches = [regex matchesInString:value options:0 range:NSMakeRange(0, [value length])];
82+
NSMutableArray<NSMutableArray<NSNumber *> *> *outputs = [NSMutableArray arrayWithCapacity:_numVals];
83+
NSUInteger size = [soutputRange count];
84+
for (NSUInteger j = 0; j < _numVals; j++) {
85+
NSMutableArray *output = [NSMutableArray arrayWithCapacity:size];
86+
[outputs addObject:output];
87+
for (int i = 0; i < size; i++) {
88+
[output addObject:[[_outputRanges objectAtIndex:i] objectAtIndex:j]];
89+
}
3090
}
91+
_outputs = [outputs copy];
3192
}
3293
_outputRange = [outputRange copy];
94+
_soutputRange = [soutputRange copy];
3395
_extrapolateLeft = config[@"extrapolateLeft"];
3496
_extrapolateRight = config[@"extrapolateRight"];
3597
}
@@ -61,11 +123,47 @@ - (void)performUpdate
61123

62124
CGFloat inputValue = _parentNode.value;
63125

64-
self.value = RCTInterpolateValueInRange(inputValue,
65-
_inputRange,
66-
_outputRange,
67-
_extrapolateLeft,
68-
_extrapolateRight);
126+
CGFloat interpolated = RCTInterpolateValueInRange(inputValue,
127+
_inputRange,
128+
_outputRange,
129+
_extrapolateLeft,
130+
_extrapolateRight);
131+
self.value = interpolated;
132+
if (_hasStringOutput) {
133+
// 'rgba(0, 100, 200, 0)'
134+
// ->
135+
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
136+
if (_numVals > 1) {
137+
NSString *text = _soutputRange[0];
138+
NSMutableString *formattedText = [NSMutableString stringWithString:text];
139+
NSUInteger i = _numVals;
140+
for (NSTextCheckingResult *match in [_matches reverseObjectEnumerator]) {
141+
CGFloat val = RCTInterpolateValueInRange(inputValue,
142+
_inputRange,
143+
_outputs[--i],
144+
_extrapolateLeft,
145+
_extrapolateRight);
146+
NSString *str;
147+
if (_shouldRound) {
148+
// rgba requires that the r,g,b are integers.... so we want to round them, but we *dont* want to
149+
// round the opacity (4th column).
150+
bool isAlpha = i == 3;
151+
CGFloat rounded = isAlpha ? round(val * 1000) / 1000 : round(val);
152+
str = isAlpha ? [NSString stringWithFormat:@"%1.3f", rounded] : [NSString stringWithFormat:@"%1.0f", rounded];
153+
} else {
154+
str = [NSString stringWithFormat:@"%1f", val];
155+
}
156+
157+
[formattedText replaceCharactersInRange:[match range] withString:str];
158+
}
159+
self.animatedObject = formattedText;
160+
} else {
161+
self.animatedObject = [regex stringByReplacingMatchesInString:_soutputRange[0]
162+
options:0
163+
range:NSMakeRange(0, _soutputRange[0].length)
164+
withTemplate:[NSString stringWithFormat:@"%1f", interpolated]];
165+
}
166+
}
69167
}
70168

71169
@end

Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m

+7-2
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,13 @@ - (void)performUpdate
110110

111111
} else if ([parentNode isKindOfClass:[RCTValueAnimatedNode class]]) {
112112
NSString *property = [self propertyNameForParentTag:parentTag];
113-
CGFloat value = [(RCTValueAnimatedNode *)parentNode value];
114-
self->_propsDictionary[property] = @(value);
113+
id animatedObject = [(RCTValueAnimatedNode *)parentNode animatedObject];
114+
if (animatedObject) {
115+
self->_propsDictionary[property] = animatedObject;
116+
} else {
117+
CGFloat value = [(RCTValueAnimatedNode *)parentNode value];
118+
self->_propsDictionary[property] = @(value);
119+
}
115120
}
116121
}
117122

Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
- (void)extractOffset;
2525

2626
@property (nonatomic, assign) CGFloat value;
27+
@property (nonatomic, strong) id animatedObject;
2728
@property (nonatomic, weak) id<RCTValueAnimatedNodeObserver> valueObserver;
2829

2930
@end

ReactAndroid/src/main/java/com/facebook/react/animated/InterpolationAnimatedNode.java

+91-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
1010
import com.facebook.react.bridge.ReadableArray;
1111
import com.facebook.react.bridge.ReadableMap;
12+
import com.facebook.react.bridge.ReadableType;
13+
14+
import java.util.ArrayList;
15+
import java.util.regex.Matcher;
16+
import java.util.regex.Pattern;
17+
1218
import javax.annotation.Nullable;
1319

1420
/**
@@ -21,6 +27,7 @@
2127
public static final String EXTRAPOLATE_TYPE_IDENTITY = "identity";
2228
public static final String EXTRAPOLATE_TYPE_CLAMP = "clamp";
2329
public static final String EXTRAPOLATE_TYPE_EXTEND = "extend";
30+
static final Pattern regex = Pattern.compile("[0-9.-]+");
2431

2532
private static double[] fromDoubleArray(ReadableArray ary) {
2633
double[] res = new double[ary.size()];
@@ -105,13 +112,68 @@ private static int findRangeIndex(double value, double[] ranges) {
105112

106113
private final double mInputRange[];
107114
private final double mOutputRange[];
115+
private String mPattern;
116+
private double mOutputs[][];
117+
private final boolean mHasStringOutput;
118+
private final Matcher mSOutputMatcher;
108119
private final String mExtrapolateLeft;
109120
private final String mExtrapolateRight;
110121
private @Nullable ValueAnimatedNode mParent;
122+
private boolean mShouldRound;
123+
private int mNumVals;
111124

112125
public InterpolationAnimatedNode(ReadableMap config) {
113126
mInputRange = fromDoubleArray(config.getArray("inputRange"));
114-
mOutputRange = fromDoubleArray(config.getArray("outputRange"));
127+
ReadableArray output = config.getArray("outputRange");
128+
mHasStringOutput = output.getType(0) == ReadableType.String;
129+
if (mHasStringOutput) {
130+
/*
131+
* Supports string shapes by extracting numbers so new values can be computed,
132+
* and recombines those values into new strings of the same shape. Supports
133+
* things like:
134+
*
135+
* rgba(123, 42, 99, 0.36) // colors
136+
* -45deg // values with units
137+
*/
138+
int size = output.size();
139+
mOutputRange = new double[size];
140+
mPattern = output.getString(0);
141+
mShouldRound = mPattern.startsWith("rgb");
142+
mSOutputMatcher = regex.matcher(mPattern);
143+
ArrayList<ArrayList<Double>> mOutputRanges = new ArrayList<>();
144+
for (int i = 0; i < size; i++) {
145+
String val = output.getString(i);
146+
Matcher m = regex.matcher(val);
147+
ArrayList<Double> outputRange = new ArrayList<>();
148+
mOutputRanges.add(outputRange);
149+
while (m.find()) {
150+
Double parsed = Double.parseDouble(m.group());
151+
outputRange.add(parsed);
152+
}
153+
mOutputRange[i] = outputRange.get(0);
154+
}
155+
156+
// ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)']
157+
// ->
158+
// [
159+
// [0, 50],
160+
// [100, 150],
161+
// [200, 250],
162+
// [0, 0.5],
163+
// ]
164+
mNumVals = mOutputRanges.get(0).size();
165+
mOutputs = new double[mNumVals][];
166+
for (int j = 0; j < mNumVals; j++) {
167+
double[] arr = new double[size];
168+
mOutputs[j] = arr;
169+
for (int i = 0; i < size; i++) {
170+
arr[i] = mOutputRanges.get(i).get(j);
171+
}
172+
}
173+
} else {
174+
mOutputRange = fromDoubleArray(output);
175+
mSOutputMatcher = null;
176+
}
115177
mExtrapolateLeft = config.getString("extrapolateLeft");
116178
mExtrapolateRight = config.getString("extrapolateRight");
117179
}
@@ -142,6 +204,33 @@ public void update() {
142204
// unattached node.
143205
return;
144206
}
145-
mValue = interpolate(mParent.getValue(), mInputRange, mOutputRange, mExtrapolateLeft, mExtrapolateRight);
207+
double value = mParent.getValue();
208+
mValue = interpolate(value, mInputRange, mOutputRange, mExtrapolateLeft, mExtrapolateRight);
209+
if (mHasStringOutput) {
210+
// 'rgba(0, 100, 200, 0)'
211+
// ->
212+
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
213+
if (mNumVals > 1) {
214+
StringBuffer sb = new StringBuffer(mPattern.length());
215+
int i = 0;
216+
mSOutputMatcher.reset();
217+
while (mSOutputMatcher.find()) {
218+
double val = interpolate(value, mInputRange, mOutputs[i++], mExtrapolateLeft, mExtrapolateRight);
219+
if (mShouldRound) {
220+
// rgba requires that the r,g,b are integers.... so we want to round them, but we *dont* want to
221+
// round the opacity (4th column).
222+
boolean isAlpha = i == 4;
223+
int rounded = (int)Math.round(isAlpha ? val * 1000 : val);
224+
mSOutputMatcher.appendReplacement(sb, isAlpha ? String.valueOf((double)rounded / 1000) : String.valueOf(rounded));
225+
} else {
226+
mSOutputMatcher.appendReplacement(sb, String.valueOf(val));
227+
}
228+
}
229+
mSOutputMatcher.appendTail(sb);
230+
mAnimatedObject = sb.toString();
231+
} else {
232+
mAnimatedObject = mSOutputMatcher.replaceFirst(String.valueOf(mValue));
233+
}
234+
}
146235
}
147236
}

0 commit comments

Comments
 (0)