diff --git a/Libraries/Animated/animations/TimingAnimation.js b/Libraries/Animated/animations/TimingAnimation.js index 3ed0e0f128825a..748bb47cc6f313 100644 --- a/Libraries/Animated/animations/TimingAnimation.js +++ b/Libraries/Animated/animations/TimingAnimation.js @@ -19,6 +19,7 @@ const {shouldUseNativeDriver} = require('../NativeAnimatedHelper'); import type {PlatformConfig} from '../AnimatedPlatformConfig'; import type {AnimationConfig, EndCallback} from './Animation'; +import type {RgbaValue} from '../nodes/AnimatedColor'; import AnimatedColor from '../nodes/AnimatedColor'; @@ -33,13 +34,7 @@ export type TimingAnimationConfig = { ... } | AnimatedValueXY - | { - r: number, - g: number, - b: number, - a: number, - ... - } + | RgbaValue | AnimatedColor | AnimatedInterpolation, easing?: (value: number) => number, diff --git a/Libraries/Animated/nodes/AnimatedColor.js b/Libraries/Animated/nodes/AnimatedColor.js index 1178a45b24c643..4a5cdc5d7485b3 100644 --- a/Libraries/Animated/nodes/AnimatedColor.js +++ b/Libraries/Animated/nodes/AnimatedColor.js @@ -15,11 +15,12 @@ import AnimatedWithChildren from './AnimatedWithChildren'; import normalizeColor from '../../StyleSheet/normalizeColor'; import {processColorObject} from '../../StyleSheet/PlatformColorValueTypes'; +import type {PlatformConfig} from '../AnimatedPlatformConfig'; import type {ColorValue} from '../../StyleSheet/StyleSheet'; import type {NativeColorValue} from '../../StyleSheet/PlatformColorValueTypes'; type ColorListenerCallback = (value: string) => mixed; -type RgbaValue = { +export type RgbaValue = { +r: number, +g: number, +b: number, @@ -263,4 +264,22 @@ export default class AnimatedColor extends AnimatedWithChildren { this.a.__removeChild(this); super.__detach(); } + + __makeNative(platformConfig: ?PlatformConfig) { + this.r.__makeNative(platformConfig); + this.g.__makeNative(platformConfig); + this.b.__makeNative(platformConfig); + this.a.__makeNative(platformConfig); + super.__makeNative(platformConfig); + } + + __getNativeConfig(): {...} { + return { + type: 'color', + r: this.r.__getNativeTag(), + g: this.g.__getNativeTag(), + b: this.b.__getNativeTag(), + a: this.a.__getNativeTag(), + }; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/BUCK b/ReactAndroid/src/main/java/com/facebook/react/animated/BUCK index 1f4e245b01f88a..2a61c4707f6135 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/BUCK @@ -28,6 +28,7 @@ rn_android_library( react_native_target("java/com/facebook/react/modules/core:core"), react_native_target("java/com/facebook/react/uimanager:uimanager"), react_native_target("java/com/facebook/react/uimanager/annotations:annotations"), + react_native_target("java/com/facebook/react/views/view:view"), ], exported_deps = [react_native_root_target(":FBReactNativeSpec")], ) diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/ColorAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/ColorAnimatedNode.java new file mode 100644 index 00000000000000..0624a866ab993b --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/ColorAnimatedNode.java @@ -0,0 +1,66 @@ +/* + * 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 com.facebook.react.bridge.ReadableMap; +import com.facebook.react.views.view.ColorUtil; + +/** Animated node that represents a color. */ +/*package*/ class ColorAnimatedNode extends AnimatedNode { + + private final NativeAnimatedNodesManager mNativeAnimatedNodesManager; + private final int mRNodeId; + private final int mGNodeId; + private final int mBNodeId; + private final int mANodeId; + private int mColor; + + public ColorAnimatedNode( + ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) { + mNativeAnimatedNodesManager = nativeAnimatedNodesManager; + mRNodeId = config.getInt("r"); + mGNodeId = config.getInt("g"); + mBNodeId = config.getInt("b"); + mANodeId = config.getInt("a"); + + // TODO (T110930421): Support platform color + } + + public int getColor() { + return mColor; + } + + @Override + public void update() { + AnimatedNode rNode = mNativeAnimatedNodesManager.getNodeById(mRNodeId); + AnimatedNode gNode = mNativeAnimatedNodesManager.getNodeById(mGNodeId); + AnimatedNode bNode = mNativeAnimatedNodesManager.getNodeById(mBNodeId); + AnimatedNode aNode = mNativeAnimatedNodesManager.getNodeById(mANodeId); + + double r = ((ValueAnimatedNode) rNode).getValue(); + double g = ((ValueAnimatedNode) gNode).getValue(); + double b = ((ValueAnimatedNode) bNode).getValue(); + double a = ((ValueAnimatedNode) aNode).getValue(); + + mColor = ColorUtil.normalize(r, g, b, a); + } + + @Override + public String prettyPrint() { + return "ColorAnimatedNode[" + + mTag + + "]: r: " + + mRNodeId + + " g: " + + mGNodeId + + " b: " + + mBNodeId + + " a: " + + mANodeId; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java index 8321acffb825e3..10252abfa11cc8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java @@ -129,6 +129,8 @@ public void createAnimatedNode(int tag, ReadableMap config) { node = new StyleAnimatedNode(config, this); } else if ("value".equals(type)) { node = new ValueAnimatedNode(config); + } else if ("color".equals(type)) { + node = new ColorAnimatedNode(config, this); } else if ("props".equals(type)) { node = new PropsAnimatedNode(config, this); } else if ("interpolation".equals(type)) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java index 9ee330d7dd0fc0..759675e661abeb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java @@ -105,6 +105,8 @@ public final void updateView() { } else { mPropMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue()); } + } else if (node instanceof ColorAnimatedNode) { + mPropMap.putInt(entry.getKey(), ((ColorAnimatedNode) node).getColor()); } else { throw new IllegalArgumentException( "Unsupported type of node used in property node " + node.getClass()); diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.java index 65b65a10edade1..68ef6a4186e967 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.java @@ -43,6 +43,8 @@ public void collectViewUpdates(JavaOnlyMap propsMap) { ((TransformAnimatedNode) node).collectViewUpdates(propsMap); } else if (node instanceof ValueAnimatedNode) { propsMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue()); + } else if (node instanceof ColorAnimatedNode) { + propsMap.putInt(entry.getKey(), ((ColorAnimatedNode) node).getColor()); } else { throw new IllegalArgumentException( "Unsupported type of node used in property node " + node.getClass()); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ColorUtil.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ColorUtil.java index cc804ef703ee3b..f1efecf3f0da9b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ColorUtil.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ColorUtil.java @@ -39,6 +39,7 @@ public static int multiplyColorAlpha(int color, int alpha) { /** * Gets the opacity from a color. Inspired by Android ColorDrawable. * + * @param color color to get opacity from * @return opacity expressed by one of PixelFormat constants */ public static int getOpacityFromColor(int color) { @@ -51,4 +52,22 @@ public static int getOpacityFromColor(int color) { return PixelFormat.TRANSLUCENT; } } + + /** + * Converts individual {r, g, b, a} channel values to a single integer representation of the color + * as 0xAARRGGBB. + * + * @param r red channel value, [0, 255] + * @param g green channel value, [0, 255] + * @param b blue channel value, [0, 255] + * @param a alpha channel value, [0, 1] + * @return integer representation of the color as 0xAARRGGBB + */ + public static int normalize(double r, double g, double b, double a) { + return (clamp255(a * 255) << 24) | (clamp255(r) << 16) | (clamp255(g) << 8) | clamp255(b); + } + + private static int clamp255(double value) { + return Math.max(0, Math.min(255, (int) Math.round(value))); + } } diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/BUCK b/ReactAndroid/src/test/java/com/facebook/react/views/BUCK index 29dc6d734de041..a8548c6f570732 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/views/BUCK @@ -2,9 +2,12 @@ load("//tools/build_defs/oss:rn_defs.bzl", "YOGA_TARGET", "react_native_dep", "r rn_robolectric_test( name = "views", - # TODO Disabled temporarily until Yoga linking is fixed t14964130 + # TODO (T110934492): Disabled temporarily until tests are fixed # srcs = glob(['**/*.java']), - srcs = glob(["image/*.java"]), + srcs = glob([ + "image/*.java", + "view/*.java", + ]), contacts = ["oncall+fbandroid_sheriff@xmail.facebook.com"], deps = [ YOGA_TARGET, diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/view/ColorUtilTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/view/ColorUtilTest.java index 5bb2bd71e05ff8..a7ea56f0e2ac65 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/view/ColorUtilTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/views/view/ColorUtilTest.java @@ -44,4 +44,13 @@ public void testGetOpacityFromColor() { assertEquals(PixelFormat.OPAQUE, ColorUtil.getOpacityFromColor(0xFF123456)); assertEquals(PixelFormat.OPAQUE, ColorUtil.getOpacityFromColor(0xFFFFFFFF)); } + + @Test + public void testNormalize() { + assertEquals(0x800B1621, ColorUtil.normalize(11, 22, 33, 0.5)); + assertEquals(0x00000000, ColorUtil.normalize(0, 0, 0, 0)); + assertEquals(0xFFFFFFFF, ColorUtil.normalize(255, 255, 255, 1)); + assertEquals(0xFF00FFFF, ColorUtil.normalize(-1, 256, 255, 1.1)); + assertEquals(0x000001FF, ColorUtil.normalize(0.4, 0.5, 255.4, -1)); + } }