Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ObjectAnimatedNode #36742

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ describe('AnimatedObject', () => {
]);
});

it('should make all AnimatedNodes native', () => {
const anim = new Animated.Value(0);
const translateAnim = anim.interpolate({
inputRange: [0, 1],
outputRange: [100, 200],
});

const node = new AnimatedObject([
{
translate: [translateAnim, translateAnim],
},
{
translateX: translateAnim,
},
{scale: anim},
]);

node.__makeNative();

expect(node.__isNative).toBe(true);
expect(anim.__isNative).toBe(true);
expect(translateAnim.__isNative).toBe(true);
});

describe('hasAnimatedNode', () => {
it('should detect any animated nodes', () => {
expect(hasAnimatedNode(10)).toBe(false);
Expand Down
16 changes: 10 additions & 6 deletions packages/react-native/Libraries/Animated/nodes/AnimatedObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,18 @@ export default class AnimatedObject extends AnimatedWithChildren {
}

__makeNative(platformConfig: ?PlatformConfig): void {
throw new Error(
'This JS animated node type cannot be used as native animated node',
);
visit(this._value, value => {
value.__makeNative(platformConfig);
});
super.__makeNative(platformConfig);
}

__getNativeConfig(): any {
throw new Error(
'This JS animated node type cannot be used as native animated node',
);
return {
type: 'object',
value: mapAnimatedNodes(this._value, node => {
return {nodeTag: node.__getNativeTag()};
}),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ public void createAnimatedNode(int tag, ReadableMap config) {
node = new TransformAnimatedNode(config, this);
} else if ("tracking".equals(type)) {
node = new TrackingAnimatedNode(config, this);
} else if ("object".equals(type)) {
node = new ObjectAnimatedNode(config, this);
} else {
throw new JSApplicationIllegalArgumentException("Unsupported node type: " + type);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.animated;

import androidx.annotation.Nullable;
import com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.bridge.JavaOnlyMap;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReadableType;

/**
* Native counterpart of object animated node (see AnimatedObject class in
* AnimatedImplementation.js)
*/
/* package */ class ObjectAnimatedNode extends AnimatedNode {

private static final String VALUE_KEY = "value";
private static final String NODE_TAG_KEY = "nodeTag";

private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final JavaOnlyMap mConfig;

ObjectAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
mConfig = JavaOnlyMap.deepClone(config);
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
}

public void collectViewUpdates(String propKey, JavaOnlyMap propsMap) {
ReadableType valueType = mConfig.getType(VALUE_KEY);
if (valueType == ReadableType.Map) {
propsMap.putMap(propKey, collectViewUpdatesHelper(mConfig.getMap(VALUE_KEY)));
} else if (valueType == ReadableType.Array) {
propsMap.putArray(propKey, collectViewUpdatesHelper(mConfig.getArray(VALUE_KEY)));
} else {
throw new IllegalArgumentException("Invalid value type for ObjectAnimatedNode");
}
}

private @Nullable JavaOnlyArray collectViewUpdatesHelper(@Nullable ReadableArray source) {
if (source == null) {
return null;
}

JavaOnlyArray result = new JavaOnlyArray();
for (int i = 0; i < source.size(); i++) {
switch (source.getType(i)) {
case Null:
result.pushNull();
break;
case Boolean:
result.pushBoolean(source.getBoolean(i));
break;
case Number:
result.pushDouble(source.getDouble(i));
break;
case String:
result.pushString(source.getString(i));
break;
case Map:
ReadableMap map = source.getMap(i);
if (map.hasKey(NODE_TAG_KEY) && map.getType(NODE_TAG_KEY) == ReadableType.Number) {
AnimatedNode node = mNativeAnimatedNodesManager.getNodeById(map.getInt(NODE_TAG_KEY));
if (node == null) {
throw new IllegalArgumentException("Mapped value node does not exist");
} else if (node instanceof ValueAnimatedNode) {
ValueAnimatedNode valueAnimatedNode = (ValueAnimatedNode) node;
Object animatedObject = valueAnimatedNode.getAnimatedObject();
if (animatedObject instanceof Integer) {
result.pushInt((Integer) animatedObject);
} else if (animatedObject instanceof String) {
result.pushString((String) animatedObject);
} else {
result.pushDouble(valueAnimatedNode.getValue());
}
} else if (node instanceof ColorAnimatedNode) {
result.pushInt(((ColorAnimatedNode) node).getColor());
}
} else {
result.pushMap(collectViewUpdatesHelper(source.getMap(i)));
}
break;
case Array:
result.pushArray(collectViewUpdatesHelper(source.getArray(i)));
break;
}
}
return result;
}

private @Nullable JavaOnlyMap collectViewUpdatesHelper(@Nullable ReadableMap source) {
if (source == null) {
return null;
}

JavaOnlyMap result = new JavaOnlyMap();
ReadableMapKeySetIterator iter = source.keySetIterator();
while (iter.hasNextKey()) {
String propKey = iter.nextKey();
switch (source.getType(propKey)) {
case Null:
result.putNull(propKey);
break;
case Boolean:
result.putBoolean(propKey, source.getBoolean(propKey));
break;
case Number:
result.putDouble(propKey, source.getDouble(propKey));
break;
case String:
result.putString(propKey, source.getString(propKey));
break;
case Map:
ReadableMap map = source.getMap(propKey);
if (map != null
&& map.hasKey(NODE_TAG_KEY)
&& map.getType(NODE_TAG_KEY) == ReadableType.Number) {
AnimatedNode node = mNativeAnimatedNodesManager.getNodeById(map.getInt(NODE_TAG_KEY));
if (node == null) {
throw new IllegalArgumentException("Mapped value node does not exist");
} else if (node instanceof ValueAnimatedNode) {
ValueAnimatedNode valueAnimatedNode = (ValueAnimatedNode) node;
Object animatedObject = valueAnimatedNode.getAnimatedObject();
if (animatedObject instanceof Integer) {
result.putInt(propKey, (Integer) animatedObject);
} else if (animatedObject instanceof String) {
result.putString(propKey, (String) animatedObject);
} else {
result.putDouble(propKey, valueAnimatedNode.getValue());
}
} else if (node instanceof ColorAnimatedNode) {
result.putInt(propKey, ((ColorAnimatedNode) node).getColor());
}
} else {
result.putMap(propKey, collectViewUpdatesHelper(map));
}
break;
case Array:
result.putArray(propKey, collectViewUpdatesHelper(source.getArray(propKey)));
break;
}
}
return result;
}

@Override
public String prettyPrint() {
return "ObjectAnimatedNode["
+ mTag
+ "]: mConfig: "
+ (mConfig != null ? mConfig.toString() : "null");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ public final void updateView() {
}
} else if (node instanceof ColorAnimatedNode) {
mPropMap.putInt(entry.getKey(), ((ColorAnimatedNode) node).getColor());
} else if (node instanceof ObjectAnimatedNode) {
((ObjectAnimatedNode) node).collectViewUpdates(entry.getKey(), mPropMap);
} else {
throw new IllegalArgumentException(
"Unsupported type of node used in property node " + node.getClass());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public void collectViewUpdates(JavaOnlyMap propsMap) {
}
} else if (node instanceof ColorAnimatedNode) {
propsMap.putInt(entry.getKey(), ((ColorAnimatedNode) node).getColor());
} else if (node instanceof ObjectAnimatedNode) {
((ObjectAnimatedNode) node).collectViewUpdates(entry.getKey(), propsMap);
} else {
throw new IllegalArgumentException(
"Unsupported type of node used in property node " + node.getClass());
Expand Down