mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: <!-- Thank you for sending the PR! We appreciate you spending the time to work on these changes. Help us understand your motivation by explaining why you decided to make this change. You can learn more about contributing to React Native here: http://facebook.github.io/react-native/docs/contributing.html Happy contributing! --> I want to animate `shadowOpacity` style property with Native Animated (`useNativeDriver: true`). This is useful for example in an ios-like navigation transition where the shadow fades in with the view that is sliding in from the side. Code comment for `STYLES_WHITELIST` says: _In general native animated implementation should support any numeric property that doesn't need to be updated through the shadow view hierarchy (all non-layout properties)._ so I just added `shadowOpacity` (and `shadowRadius` too because why not?). Before this change, setting `shadowOpacity` (or `shadowRadius`) to an `AnimatedValue` (with `useNativeDriver: true`) would throw the error: `Style property 'shadowOpacity' is not supported by the native animated module`. After adding `shadowOpacity` (and `shadowRadius`), there is no error. The animation looks correct so it seems to be working. I also tried setting a ridiculously large `shadowRadius` and could see that working too. Please advice on any further testing I should do. [IOS] [ENHANCEMENT] [NativeAnimated] - Allow `shadowRadius` and `shadowOpacity` as NativeAnimated style properties. Closes https://github.com/facebook/react-native/pull/16603 Differential Revision: D6195364 Pulled By: hramos fbshipit-source-id: a55630df43df3c8f9db9921dab0bfbf925b6a09f
257 lines
7.2 KiB
JavaScript
257 lines
7.2 KiB
JavaScript
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
* @providesModule NativeAnimatedHelper
|
|
* @flow
|
|
* @format
|
|
*/
|
|
'use strict';
|
|
|
|
const NativeAnimatedModule = require('NativeModules').NativeAnimatedModule;
|
|
const NativeEventEmitter = require('NativeEventEmitter');
|
|
|
|
const invariant = require('fbjs/lib/invariant');
|
|
|
|
import type {AnimationConfig} from './animations/Animation';
|
|
import type {EventConfig} from './AnimatedEvent';
|
|
|
|
let __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */
|
|
let __nativeAnimationIdCount = 1; /* used for started animations */
|
|
|
|
type EndResult = {finished: boolean};
|
|
type EndCallback = (result: EndResult) => void;
|
|
type EventMapping = {
|
|
nativeEventPath: Array<string>,
|
|
animatedValueTag: ?number,
|
|
};
|
|
|
|
let nativeEventEmitter;
|
|
|
|
/**
|
|
* Simple wrappers around NativeAnimatedModule to provide flow and autocmplete support for
|
|
* the native module methods
|
|
*/
|
|
const API = {
|
|
createAnimatedNode: function(tag: ?number, config: Object): void {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.createAnimatedNode(tag, config);
|
|
},
|
|
startListeningToAnimatedNodeValue: function(tag: ?number) {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.startListeningToAnimatedNodeValue(tag);
|
|
},
|
|
stopListeningToAnimatedNodeValue: function(tag: ?number) {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.stopListeningToAnimatedNodeValue(tag);
|
|
},
|
|
connectAnimatedNodes: function(parentTag: ?number, childTag: ?number): void {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.connectAnimatedNodes(parentTag, childTag);
|
|
},
|
|
disconnectAnimatedNodes: function(
|
|
parentTag: ?number,
|
|
childTag: ?number,
|
|
): void {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.disconnectAnimatedNodes(parentTag, childTag);
|
|
},
|
|
startAnimatingNode: function(
|
|
animationId: ?number,
|
|
nodeTag: ?number,
|
|
config: Object,
|
|
endCallback: EndCallback,
|
|
): void {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.startAnimatingNode(
|
|
animationId,
|
|
nodeTag,
|
|
config,
|
|
endCallback,
|
|
);
|
|
},
|
|
stopAnimation: function(animationId: ?number) {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.stopAnimation(animationId);
|
|
},
|
|
setAnimatedNodeValue: function(nodeTag: ?number, value: ?number): void {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.setAnimatedNodeValue(nodeTag, value);
|
|
},
|
|
setAnimatedNodeOffset: function(nodeTag: ?number, offset: ?number): void {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.setAnimatedNodeOffset(nodeTag, offset);
|
|
},
|
|
flattenAnimatedNodeOffset: function(nodeTag: ?number): void {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.flattenAnimatedNodeOffset(nodeTag);
|
|
},
|
|
extractAnimatedNodeOffset: function(nodeTag: ?number): void {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.extractAnimatedNodeOffset(nodeTag);
|
|
},
|
|
connectAnimatedNodeToView: function(
|
|
nodeTag: ?number,
|
|
viewTag: ?number,
|
|
): void {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.connectAnimatedNodeToView(nodeTag, viewTag);
|
|
},
|
|
disconnectAnimatedNodeFromView: function(
|
|
nodeTag: ?number,
|
|
viewTag: ?number,
|
|
): void {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.disconnectAnimatedNodeFromView(nodeTag, viewTag);
|
|
},
|
|
dropAnimatedNode: function(tag: ?number): void {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.dropAnimatedNode(tag);
|
|
},
|
|
addAnimatedEventToView: function(
|
|
viewTag: ?number,
|
|
eventName: string,
|
|
eventMapping: EventMapping,
|
|
) {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.addAnimatedEventToView(
|
|
viewTag,
|
|
eventName,
|
|
eventMapping,
|
|
);
|
|
},
|
|
removeAnimatedEventFromView(
|
|
viewTag: ?number,
|
|
eventName: string,
|
|
animatedNodeTag: ?number,
|
|
) {
|
|
assertNativeAnimatedModule();
|
|
NativeAnimatedModule.removeAnimatedEventFromView(
|
|
viewTag,
|
|
eventName,
|
|
animatedNodeTag,
|
|
);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Styles allowed by the native animated implementation.
|
|
*
|
|
* In general native animated implementation should support any numeric property that doesn't need
|
|
* to be updated through the shadow view hierarchy (all non-layout properties).
|
|
*/
|
|
const STYLES_WHITELIST = {
|
|
opacity: true,
|
|
transform: true,
|
|
/* ios styles */
|
|
shadowOpacity: true,
|
|
shadowRadius: true,
|
|
/* legacy android transform properties */
|
|
scaleX: true,
|
|
scaleY: true,
|
|
translateX: true,
|
|
translateY: true,
|
|
};
|
|
|
|
const TRANSFORM_WHITELIST = {
|
|
translateX: true,
|
|
translateY: true,
|
|
scale: true,
|
|
scaleX: true,
|
|
scaleY: true,
|
|
rotate: true,
|
|
rotateX: true,
|
|
rotateY: true,
|
|
perspective: true,
|
|
};
|
|
|
|
function validateTransform(configs: Array<Object>): void {
|
|
configs.forEach(config => {
|
|
if (!TRANSFORM_WHITELIST.hasOwnProperty(config.property)) {
|
|
throw new Error(
|
|
`Property '${config.property}' is not supported by native animated module`,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
function validateStyles(styles: Object): void {
|
|
for (var key in styles) {
|
|
if (!STYLES_WHITELIST.hasOwnProperty(key)) {
|
|
throw new Error(
|
|
`Style property '${key}' is not supported by native animated module`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function validateInterpolation(config: Object): void {
|
|
var SUPPORTED_INTERPOLATION_PARAMS = {
|
|
inputRange: true,
|
|
outputRange: true,
|
|
extrapolate: true,
|
|
extrapolateRight: true,
|
|
extrapolateLeft: true,
|
|
};
|
|
for (var key in config) {
|
|
if (!SUPPORTED_INTERPOLATION_PARAMS.hasOwnProperty(key)) {
|
|
throw new Error(
|
|
`Interpolation property '${key}' is not supported by native animated module`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function generateNewNodeTag(): number {
|
|
return __nativeAnimatedNodeTagCount++;
|
|
}
|
|
|
|
function generateNewAnimationId(): number {
|
|
return __nativeAnimationIdCount++;
|
|
}
|
|
|
|
function assertNativeAnimatedModule(): void {
|
|
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
|
}
|
|
|
|
let _warnedMissingNativeAnimated = false;
|
|
|
|
function shouldUseNativeDriver(config: AnimationConfig | EventConfig): boolean {
|
|
if (config.useNativeDriver && !NativeAnimatedModule) {
|
|
if (!_warnedMissingNativeAnimated) {
|
|
console.warn(
|
|
'Animated: `useNativeDriver` is not supported because the native ' +
|
|
'animated module is missing. Falling back to JS-based animation. To ' +
|
|
'resolve this, add `RCTAnimation` module to this app, or remove ' +
|
|
'`useNativeDriver`. ' +
|
|
'More info: https://github.com/facebook/react-native/issues/11094#issuecomment-263240420',
|
|
);
|
|
_warnedMissingNativeAnimated = true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return config.useNativeDriver || false;
|
|
}
|
|
|
|
module.exports = {
|
|
API,
|
|
validateStyles,
|
|
validateTransform,
|
|
validateInterpolation,
|
|
generateNewNodeTag,
|
|
generateNewAnimationId,
|
|
assertNativeAnimatedModule,
|
|
shouldUseNativeDriver,
|
|
get nativeEventEmitter() {
|
|
if (!nativeEventEmitter) {
|
|
nativeEventEmitter = new NativeEventEmitter(NativeAnimatedModule);
|
|
}
|
|
return nativeEventEmitter;
|
|
},
|
|
};
|