Skip to content

Commit

Permalink
Support color animation with native driver for Android
Browse files Browse the repository at this point in the history
Summary:
Adds support for Animated.Color with native driver for Android. Reads the native config for the rbga channel AnimatedNodes, and on update(), converts the values into an integer (0xaarrggbb)

Followup changes will include support for iOS and platform colors.

Changelog:
[Android][Added] - Support running animations with AnimatedColor with native driver

Reviewed By: javache

Differential Revision: D33833600

fbshipit-source-id: 2bf05c9715b603cf014ace09e9308b2bfd67f30a
  • Loading branch information
genkikondo authored and facebook-github-bot committed Jan 29, 2022
1 parent 0ab0c5a commit 3f49e67
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 10 deletions.
9 changes: 2 additions & 7 deletions Libraries/Animated/animations/TimingAnimation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -33,13 +34,7 @@ export type TimingAnimationConfig = {
...
}
| AnimatedValueXY
| {
r: number,
g: number,
b: number,
a: number,
...
}
| RgbaValue
| AnimatedColor
| AnimatedInterpolation,
easing?: (value: number) => number,
Expand Down
21 changes: 20 additions & 1 deletion Libraries/Animated/nodes/AnimatedColor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")],
)
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)));
}
}
7 changes: 5 additions & 2 deletions ReactAndroid/src/test/java/com/facebook/react/views/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ["[email protected]"],
deps = [
YOGA_TARGET,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

0 comments on commit 3f49e67

Please sign in to comment.