Skip to content

Commit

Permalink
Added getAnimatableRef function (#4533)
Browse files Browse the repository at this point in the history
## Summary

Added an option for a component to specify which other component should
be animated when `createAnimatedComponent` is executed on it by adding
`getAnimatableRef` function (we might want to think of a better name).

This is useful for some 3rd party libraries which want to add support
for animating their component, but it's wrapped inside of another
component which is inaccessible to the user.

## Test plan

Included a simple example screen where inner `Rect` component is
animated when `createAnimatedComponent` is executed on the component
containing it.


https://github.com/software-mansion/react-native-reanimated/assets/31368152/96d2cd19-a4f9-48ac-bd23-f50746dc7e0b
  • Loading branch information
behenate authored Jun 7, 2023
1 parent d6d3a55 commit 2426f32
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 2 deletions.
82 changes: 82 additions & 0 deletions app/src/examples/AnimatableRefExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { useState } from 'react';
import { View, StyleSheet, Button } from 'react-native';

import { Rect, Svg } from 'react-native-svg';
import Animated, {
useAnimatedProps,
useSharedValue,
withSpring,
} from 'react-native-reanimated';

type SwitchProps = {
y?: number;
};

class Switch extends React.Component<SwitchProps> {
rectRef: Rect | null = null;

// When an animated version of the Switch is created we want to animate the inner Rect instead of the outer Svg component.
getAnimatableRef() {
return this.rectRef;
}

render() {
return (
<Svg height="310" width="70">
<Rect
x="5"
y="5"
width="60"
height="300"
fill="none"
stroke={'black'}
strokeWidth={10}
/>
<Rect
x="10"
y={this.props.y}
width="50"
height="40"
fill="red"
ref={(component) => {
this.rectRef = component;
}}
/>
</Svg>
);
}
}

const AnimatedSwitch = Animated.createAnimatedComponent(Switch);
export default function AnimatableRefExample() {
const [isUp, setIsUp] = useState(true);
const sv = useSharedValue(0);

const animatedProps = useAnimatedProps(() => {
return {
y: sv.value + 10,
};
});

return (
<View style={styles.container}>
<AnimatedSwitch animatedProps={animatedProps} />
<Button
onPress={() => {
sv.value = withSpring(isUp ? 250 : 0, { damping: 18 });
setIsUp(!isUp);
}}
title={`Go ${isUp ? 'down' : 'up'}`}
/>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingBottom: 200,
},
});
6 changes: 6 additions & 0 deletions app/src/examples/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AboutExample from './AboutExample';
import AnimatableRefExample from './AnimatableRefExample';
import AnimatedKeyboardExample from './AnimatedKeyboardExample';
import AnimatedListExample from './LayoutAnimations/AnimatedList';
import AnimatedSensorExample from './AnimatedSensorExample';
Expand Down Expand Up @@ -154,6 +155,11 @@ export const EXAMPLES: Record<string, Example> = {

// Basic examples

AnimatableRefExample: {
icon: '⏬',
title: 'Animate inner component',
screen: AnimatableRefExample,
},
AnimatedTextInputExample: {
icon: '🎰',
title: 'Counter',
Expand Down
10 changes: 8 additions & 2 deletions src/createAnimatedComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ type Options<P> = {
interface ComponentRef extends Component {
setNativeProps?: (props: Record<string, unknown>) => void;
getScrollableNode?: () => ComponentRef;
getAnimatableRef?: () => ComponentRef;
}

export interface InitialComponentProps extends Record<string, unknown> {
Expand Down Expand Up @@ -392,14 +393,19 @@ export default function createAnimatedComponent(
let viewName: string | null;
let shadowNodeWrapper: ShadowNodeWrapper | null = null;
let viewConfig;
// Component can specify ref which should be animated when animated version of the component is created.
// Otherwise, we animate the component itself.
const component = this._component?.getAnimatableRef
? this._component.getAnimatableRef()
: this;
if (Platform.OS === 'web') {
viewTag = findNodeHandle(this);
viewTag = findNodeHandle(component);
viewName = null;
shadowNodeWrapper = null;
viewConfig = null;
} else {
// hostInstance can be null for a component that doesn't render anything (render function returns null). Example: svg Stop: https://github.com/react-native-svg/react-native-svg/blob/develop/src/elements/Stop.tsx
const hostInstance = RNRenderer.findHostInstance_DEPRECATED(this);
const hostInstance = RNRenderer.findHostInstance_DEPRECATED(component);
if (!hostInstance) {
throw new Error(
'Cannot find host instance for this component. Maybe it renders nothing?'
Expand Down

0 comments on commit 2426f32

Please sign in to comment.